[{"data":1,"prerenderedAt":97760},["ShallowReactive",2],{"navigation":3,"all-posts":714,"blog":97750},[4,70,203,300,701],{"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],{"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},"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":128,"path":129,"stem":130},"Office Hours with MusicTech Lab's DDEX Expert","/blog/music-data/ddex-office-hours-musictech","blog/music-data/ddex-office-hours-musictech",{"title":132,"path":133,"stem":134},"DDEX Open Source Projects Review","/blog/music-data/ddex-open-source-projects-review","blog/music-data/ddex-open-source-projects-review",{"title":136,"path":137,"stem":138},"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":140,"path":141,"stem":142},"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":144,"path":145,"stem":146},"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":148,"path":149,"stem":150},"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":152,"path":153,"stem":154},"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":156,"path":157,"stem":158},"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":160,"path":161,"stem":162},"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":164,"path":165,"stem":166},"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":168,"path":169,"stem":170},"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":172,"path":173,"stem":174},"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":176,"path":177,"stem":178},"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":180,"path":181,"stem":182},"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":184,"path":185,"stem":186},"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":188,"path":189,"stem":190},"Understanding the API First Approach","/blog/music-data/understanding-the-api-first-approach","blog/music-data/understanding-the-api-first-approach",{"title":192,"path":193,"stem":194},"The Voice Memo Graveyard Problem","/blog/music-data/voice-memo-graveyard-problem","blog/music-data/voice-memo-graveyard-problem",{"title":196,"path":197,"stem":198},"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":200,"path":201,"stem":202},"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":204,"path":205,"stem":206,"children":207,"page":69},"Newsletter","/blog/newsletter","blog/newsletter",[208,212,216,220,224,228,232,236,240,244,248,252,256,260,264,268,272,276,280,284,288,292,296],{"title":209,"path":210,"stem":211},"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":213,"path":214,"stem":215},"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":217,"path":218,"stem":219},"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":221,"path":222,"stem":223},"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":225,"path":226,"stem":227},"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":229,"path":230,"stem":231},"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":233,"path":234,"stem":235},"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":237,"path":238,"stem":239},"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":241,"path":242,"stem":243},"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":245,"path":246,"stem":247},"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":249,"path":250,"stem":251},"Music Industry Tech Openings (May 2025 Update)","/blog/newsletter/music-industry-tech-openings-may-2025","blog/newsletter/music-industry-tech-openings-may-2025",{"title":253,"path":254,"stem":255},"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":257,"path":258,"stem":259},"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":261,"path":262,"stem":263},"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":265,"path":266,"stem":267},"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":269,"path":270,"stem":271},"Feeling the MusicTech Momentum","/blog/newsletter/musictech-insights-2-curated-by-maciej-dulski","blog/newsletter/musictech-insights-2-curated-by-maciej-dulski",{"title":273,"path":274,"stem":275},"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":277,"path":278,"stem":279},"The Music Metadata Conundrum","/blog/newsletter/musictech-insights-4-curated-by-amanda-schupf","blog/newsletter/musictech-insights-4-curated-by-amanda-schupf",{"title":281,"path":282,"stem":283},"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":285,"path":286,"stem":287},"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":289,"path":290,"stem":291},"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":293,"path":294,"stem":295},"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":297,"path":298,"stem":299},"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":301,"path":302,"stem":303,"children":304,"page":69},"Software Development","/blog/software-development","blog/software-development",[305,309,313,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],{"title":306,"path":307,"stem":308},"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":310,"path":311,"stem":312},"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":314,"path":315,"stem":316},"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":318,"path":319,"stem":320},"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":322,"path":323,"stem":324},"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":326,"path":327,"stem":328},"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":330,"path":331,"stem":332},"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":334,"path":335,"stem":336},"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":338,"path":339,"stem":340},"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":342,"path":343,"stem":344},"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":346,"path":347,"stem":348},"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":350,"path":351,"stem":352},"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":354,"path":355,"stem":356},"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":358,"path":359,"stem":360},"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":362,"path":363,"stem":364},"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":366,"path":367,"stem":368},"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":370,"path":371,"stem":372},"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":374,"path":375,"stem":376},"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":378,"path":379,"stem":380},"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":382,"path":383,"stem":384},"Bravelab's team about productivity","/blog/software-development/bravelabs-team-about-productivity","blog/software-development/bravelabs-team-about-productivity",{"title":386,"path":387,"stem":388},"Braveloper","/blog/software-development/braveloper","blog/software-development/braveloper",{"title":390,"path":391,"stem":392},"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":394,"path":395,"stem":396},"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":398,"path":399,"stem":400},"Change Detection mechanism in Angular","/blog/software-development/change-detection-mechanism-in-angular","blog/software-development/change-detection-mechanism-in-angular",{"title":402,"path":403,"stem":404},"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":406,"path":407,"stem":408},"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":410,"path":411,"stem":412},"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":414,"path":415,"stem":416},"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":418,"path":419,"stem":420},"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":422,"path":423,"stem":424},"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":426,"path":427,"stem":428},"Dev Meeting 003: Web3 Primer","/blog/software-development/dev-meeting-003-web3-primer","blog/software-development/dev-meeting-003-web3-primer",{"title":430,"path":431,"stem":432},"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":434,"path":435,"stem":436},"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":438,"path":439,"stem":440},"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":442,"path":443,"stem":444},"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":446,"path":447,"stem":448},"Django-cms and GraphQL","/blog/software-development/django-cms-and-graphql","blog/software-development/django-cms-and-graphql",{"title":450,"path":451,"stem":452},"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":454,"path":455,"stem":456},"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":458,"path":459,"stem":460},"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":462,"path":463,"stem":464},"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":466,"path":467,"stem":468},"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":470,"path":471,"stem":472},"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":474,"path":475,"stem":476},"Hasura in action. How to use it with Django","/blog/software-development/hasura-in-action","blog/software-development/hasura-in-action",{"title":478,"path":479,"stem":480},"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":482,"path":483,"stem":484},"How does JavaScript work","/blog/software-development/how-does-javascript-work","blog/software-development/how-does-javascript-work",{"title":486,"path":487,"stem":488},"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":490,"path":491,"stem":492},"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":494,"path":495,"stem":496},"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":498,"path":499,"stem":500},"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":502,"path":503,"stem":504},"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":506,"path":507,"stem":508},"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":510,"path":511,"stem":512},"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":514,"path":515,"stem":516},"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":518,"path":519,"stem":520},"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":522,"path":523,"stem":524},"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":526,"path":527,"stem":528},"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":530,"path":531,"stem":532},"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":534,"path":535,"stem":536},"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":538,"path":539,"stem":540},"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":542,"path":543,"stem":544},"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":546,"path":547,"stem":548},"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":550,"path":551,"stem":552},"Introducing MusicTech Poland","/blog/software-development/introducing-musictech-poland","blog/software-development/introducing-musictech-poland",{"title":554,"path":555,"stem":556},"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":558,"path":559,"stem":560},"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":562,"path":563,"stem":564},"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":566,"path":567,"stem":568},"JS Frameworks: Trends and Opportunities","/blog/software-development/javascript-trending-frameworks-and-market-opportunities","blog/software-development/javascript-trending-frameworks-and-market-opportunities",{"title":570,"path":571,"stem":572},"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":574,"path":575,"stem":576},"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":578,"path":579,"stem":580},"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":582,"path":583,"stem":584},"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":586,"path":587,"stem":588},"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":590,"path":591,"stem":592},"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":594,"path":595,"stem":596},"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":598,"path":599,"stem":600},"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":602,"path":603,"stem":604},"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":606,"path":607,"stem":608},"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":610,"path":611,"stem":612},"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":614,"path":615,"stem":616},"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":618,"path":619,"stem":620},"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":622,"path":623,"stem":624},"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":626,"path":627,"stem":628},"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":630,"path":631,"stem":632},"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":634,"path":635,"stem":636},"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":638,"path":639,"stem":640},"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":642,"path":643,"stem":644},"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":646,"path":647,"stem":648},"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":650,"path":651,"stem":652},"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":654,"path":655,"stem":656},"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":658,"path":659,"stem":660},"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":662,"path":663,"stem":664},"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":666,"path":667,"stem":668},"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":670,"path":671,"stem":672},"What is a Discovery Document?","/blog/software-development/what-is-discovery-document","blog/software-development/what-is-discovery-document",{"title":674,"path":675,"stem":676},"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":678,"path":679,"stem":680},"What is a Watermarked Song?","/blog/software-development/what-is-watermarked-song","blog/software-development/what-is-watermarked-song",{"title":682,"path":683,"stem":684},"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":686,"path":687,"stem":688},"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":690,"path":691,"stem":692},"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":694,"path":695,"stem":696},"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":698,"path":699,"stem":700},"Why we use Sanity.io","/blog/software-development/why-we-use-sanity-io","blog/software-development/why-we-use-sanity-io",{"title":702,"path":703,"stem":704,"children":705,"page":69},"Sportstech","/blog/sportstech","blog/sportstech",[706,710],{"title":707,"path":708,"stem":709},"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":711,"path":712,"stem":713},"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",[715,747,770,791,810,830,1739,3194,3213,3231,3863,4992,5525,5687,7510,8752,9737,11630,12422,12900,13571,14107,14806,15281,16570,18786,25874,26064,27929,30083,31815,35174,37705,40857,43003,43247,44501,45317,46474,46939,50089,50275,50532,51073,51260,51384,51499,51640,51782,51888,51985,52278,53743,55141,56496,58283,58392,59384,63953,64314,65635,66121,66968,67628,68867,69456,69734,69854,70985,71113,72027,72465,73555,74340,74617,76092,77558,78359,78662,78906,79156,79296,79445,79742,79860,82669,82856,83057,84163,84242,84537,84743,85110,85380,85487,85576,86372,86500,86830,87077,87348,87610,87792,87951,88032,88198,88325,88565,88706,88882,89124,89294,89406,89636,89788,90082,90221,90575,90668,90768,90888,90996,91123,91228,91404,91572,91635,91922,92004,92262,92330,92438,92536,92823,93026,93109,93183,93329,93452,93510,93613,93719,93811,93866,93972,94029,94096,94272,94380,94444,94488,94572,94753,94890,94947,95182,95387,95632,96056,96204,96343,96483,96578,96663,96720,96834,96921,96985,97322,97502,97594],{"id":716,"title":88,"authors":717,"badge":723,"body":724,"category":731,"client":723,"date":732,"description":733,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":735,"keyTakeaways":723,"meta":737,"navigation":738,"path":89,"seo":739,"status":740,"stem":90,"tags":741,"teaser":745,"__hash__":746},"posts/blog/music-data/ai-rehearsal-spaced-repetition-for-musical-ideas.md",[718],{"name":719,"to":720,"avatar":721},"Mariusz Smenzyk","https://www.linkedin.com/in/mariusz-smenzyk/",{"src":722},"/images/people/mariusz-smenzyk2.webp",null,{"type":725,"value":726,"toc":727},"minimark",[],{"title":728,"searchDepth":729,"depth":729,"links":730},"",2,[],"music-data","2026-06-01T00:00:00.000Z","What if your voice memos could rehearse themselves back to you at the right moment? Applying spaced repetition — the technique behind language learning apps — to musical ideation.","md",{"src":736},"/images/blog/musictechlab_blog_ai-rehearsal.webp",{},true,{"title":88,"description":733},"upcoming",[731,742,743,744],"ai","creativity","spaced-repetition","We'll present the concept of AI-powered idea resurfacing — an Anki for music sketches. Nobody is doing this, and we think it has legs.","eulUPi4sL79UUhLrK-kcDT-qHYBKTp5-iYZaYYJjiko",{"id":748,"title":410,"authors":749,"badge":723,"body":752,"category":756,"client":723,"date":757,"description":758,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":759,"keyTakeaways":723,"meta":761,"navigation":738,"path":411,"seo":762,"status":740,"stem":412,"tags":763,"teaser":768,"__hash__":769},"posts/blog/software-development/cross-platform-problem-for-creators.md",[750],{"name":719,"to":720,"avatar":751},{"src":722},{"type":725,"value":753,"toc":754},[],{"title":728,"searchDepth":729,"depth":729,"links":755},[],"software-development","2026-05-22T00:00:00.000Z","Ideas happen anywhere — rehearsal room, commute, studio. But syncing between devices is manual and error-prone. Offline support and conflict resolution are nonexistent.",{"src":760},"/images/blog/musictechlab_blog_cross-platform-creators.webp",{},{"title":410,"description":758},[764,765,766,767],"music-tech","mobile","cross-platform","sync","We'll explore the gap between capturing an idea on your phone and working on it in the studio — and why no tool has solved this properly yet.","lMykAOfi6ZnzrGoQwiTRS3ZIXCTLtU-rBei1PqZ3DLg",{"id":771,"title":104,"authors":772,"badge":723,"body":775,"category":731,"client":723,"date":779,"description":780,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":781,"keyTakeaways":723,"meta":783,"navigation":738,"path":105,"seo":784,"status":740,"stem":106,"tags":785,"teaser":789,"__hash__":790},"posts/blog/music-data/broken-feedback-loop-music-collaboration.md",[773],{"name":719,"to":720,"avatar":774},{"src":722},{"type":725,"value":776,"toc":777},[],{"title":728,"searchDepth":729,"depth":729,"links":778},[],"2026-05-15T00:00:00.000Z","Sharing a rough idea with a collaborator requires exporting, uploading, sending a link, then getting feedback via text. There's no Google Docs-style commenting for audio.",{"src":782},"/images/blog/musictechlab_blog_broken-feedback-loop.webp",{},{"title":104,"description":780},[764,786,787,788],"collaboration","audio","workflow","We'll break down why early-stage music collaboration is stuck in email/Dropbox workflows, and what a purpose-built feedback system for audio would look like.","pAu-d12ERlpdiPRo3RGjGEYKeWrQN4_82-4o3c8whjQ",{"id":792,"title":92,"authors":793,"badge":723,"body":796,"category":731,"client":723,"date":800,"description":801,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":802,"keyTakeaways":723,"meta":804,"navigation":738,"path":93,"seo":805,"status":740,"stem":94,"tags":806,"teaser":808,"__hash__":809},"posts/blog/music-data/audio-project-organization-mess.md",[794],{"name":719,"to":720,"avatar":795},{"src":722},{"type":725,"value":797,"toc":798},[],{"title":728,"searchDepth":729,"depth":729,"links":799},[],"2026-05-10T00:00:00.000Z","Projects, folders, sub-recordings, and versions pile up with no structure. Search and filtering across recordings is missing. No auto-titling or smart tagging — everything is 'Recording 47'.",{"src":803},"/images/blog/musictechlab_blog_audio-project-organization.webp",{},{"title":92,"description":801},[731,787,807,788],"organization","We'll explore why musicians drown in unstructured audio files, why manual tagging always fails, and what an ideal project workspace for music creators would look like.","yzu7hUOUThxdS4j4-pCAyCx6EAS9w_cIJKZhgiIrXCk",{"id":811,"title":96,"authors":812,"badge":723,"body":815,"category":731,"client":723,"date":819,"description":820,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":821,"keyTakeaways":723,"meta":823,"navigation":738,"path":97,"seo":824,"status":740,"stem":98,"tags":825,"teaser":828,"__hash__":829},"posts/blog/music-data/audio-search-broken-fix-with-embeddings.md",[813],{"name":719,"to":720,"avatar":814},{"src":722},{"type":725,"value":816,"toc":817},[],{"title":728,"searchDepth":729,"depth":729,"links":818},[],"2026-05-01T00:00:00.000Z","Tags don't work. Manual labeling doesn't scale. Even on Slack and WhatsApp, finding information in voice messages is nearly impossible. Embeddings change everything.",{"src":822},"/images/blog/musictechlab_blog_audio-search-embeddings.webp",{},{"title":96,"description":820},[731,787,826,742,827],"embeddings","search","From CLAP to Whisper + vector search — we'll cover why traditional audio search fails and what a modern, embedding-powered approach looks like in practice.","Qp-4ZILpkXKkzxQVXYyUPx01PAC8pAEzV7W4gh7f8UM",{"id":831,"title":574,"authors":832,"badge":836,"body":839,"category":756,"client":723,"date":1697,"description":1698,"extension":734,"faq":1699,"featured":738,"featuredOrder":1715,"hidden":69,"image":1716,"keyTakeaways":1719,"meta":1729,"navigation":738,"path":575,"seo":1730,"status":723,"stem":576,"tags":1733,"teaser":723,"__hash__":1738},"posts/blog/software-development/mcp-verified-human-cert-open-source.md",[833],{"name":834,"to":720,"avatar":835},"Mariusz Smenżyk",{"src":722},{"label":837,"color":838},"Open Source","#7c3aed",{"type":725,"value":840,"toc":1683},[841,853,862,867,870,951,954,957,977,981,990,1012,1021,1024,1031,1037,1041,1044,1069,1073,1078,1119,1123,1134,1292,1296,1299,1316,1323,1329,1333,1342,1366,1492,1568,1571,1577,1581,1594,1624,1628,1636,1639,1643,1679],[842,843,844,845,852],"p",{},"The line between human-made and machine-generated music is getting blurry. ",[846,847,851],"a",{"href":848,"rel":849},"https://verifiedhumancert.com",[850],"nofollow","Verified Human Cert"," (VHC) is one answer to that problem: a public registry where artists and labels certify that their tracks were created by humans.",[842,854,855,856,861],{},"We wanted to query that registry without leaving our terminal. So we built an MCP server for it and open-sourced it: ",[846,857,860],{"href":858,"rel":859},"https://github.com/musictechlab/mcp-verifiedhumancert",[850],"mcp-verifiedhumancert",".",[863,864,866],"h2",{"id":865},"what-the-server-does","What the server does",[842,868,869],{},"The server exposes six tools that map directly to the VHC public API:",[871,872,873,886],"table",{},[874,875,876],"thead",{},[877,878,879,883],"tr",{},[880,881,882],"th",{},"Tool",[880,884,885],{},"What it does",[887,888,889,901,911,921,931,941],"tbody",{},[877,890,891,898],{},[892,893,894],"td",{},[895,896,897],"code",{},"vhc_verify_isrc",[892,899,900],{},"Verify a certification by ISRC code",[877,902,903,908],{},[892,904,905],{},[895,906,907],{},"vhc_verify_track",[892,909,910],{},"Check certification status by artist + track name",[877,912,913,918],{},[892,914,915],{},[895,916,917],{},"vhc_verify_cert",[892,919,920],{},"Look up a certification by cert number",[877,922,923,928],{},[892,924,925],{},[895,926,927],{},"vhc_registry",[892,929,930],{},"List recently issued certifications",[877,932,933,938],{},[892,934,935],{},[895,936,937],{},"vhc_stats",[892,939,940],{},"Platform statistics, tier breakdowns, totals",[877,942,943,948],{},[892,944,945],{},[895,946,947],{},"vhc_pricing",[892,949,950],{},"Current pricing and bundle options",[842,952,953],{},"No API key needed. The VHC registry is public, and all these endpoints are read-only.",[842,955,956],{},"Once connected, you just talk to Claude:",[958,959,960,967,972],"ul",{},[961,962,963],"li",{},[964,965,966],"em",{},"\"Is ISRC USHM82148308 certified as human-made?\"",[961,968,969],{},[964,970,971],{},"\"Check if 'Yesterday' by The Beatles has a VHC certification\"",[961,973,974],{},[964,975,976],{},"\"Show me the latest certified tracks\"",[863,978,980],{"id":979},"the-multi-agent-workflow","The multi-agent workflow",[842,982,983,984,989],{},"This is where it gets interesting. We already had ",[846,985,988],{"href":986,"rel":987},"https://github.com/musictechlab/mcp-metadata",[850],"mcp-metadata",", our open-source MCP server for reading and writing audio file metadata (ID3 tags, ISRC codes, Vorbis comments). Combining the two servers creates a multi-agent pipeline:",[991,992,993,1000,1006],"ol",{},[961,994,995,999],{},[996,997,998],"strong",{},"Agent 1"," (mcp-metadata) reads the ISRC code from an audio file",[961,1001,1002,1005],{},[996,1003,1004],{},"Agent 2"," (mcp-verifiedhumancert) verifies that ISRC against the VHC registry",[961,1007,1008,1011],{},[996,1009,1010],{},"Claude"," orchestrates both agents and presents the result",[1013,1014,1019],"pre",{"className":1015,"code":1017,"language":1018,"meta":728},[1016],"language-text","User: \"Read the ISRC from song.flac and check if it's certified\"\n\nAgent 1 (mcp-metadata): metadata_read(\"song.flac\") -> ISRC: USHM82148308\nAgent 2 (mcp-verifiedhumancert): vhc_verify_isrc(\"USHM82148308\") -> certified: true\n","text",[895,1020,1017],{"__ignoreMap":728},[842,1022,1023],{},"Two MCP servers, two specialized agents, one orchestrator. No glue code, no custom integrations. Claude handles the coordination.",[842,1025,1026],{},[1027,1028],"img",{"alt":1029,"src":1030},"Multi-agent workflow: mcp-metadata reads ISRC from an audio file, then mcp-verifiedhumancert checks the VHC registry","/images/blog/musictechlab_blog_vhc_mcp_multi_agent.webp",[1032,1033,1034],"tip",{},[842,1035,1036],{},"This pattern scales to any number of MCP servers. Each server handles one domain, and Claude routes between them based on what the user asks.",[863,1038,1040],{"id":1039},"why-this-matters-for-the-music-industry","Why this matters for the music industry",[842,1042,1043],{},"The rise of generative audio tools has created a trust problem. Listeners, labels, and platforms need a way to distinguish human-created work from machine-generated output. VHC provides that layer of trust, and our MCP server makes it accessible to developers and tooling without building custom API integrations.",[1045,1046,1053,1059,1064],"div",{"className":1047},[1048,1049,1050,1051,1052],"grid","grid-cols-1","md:grid-cols-3","gap-4","my-8",[1054,1055],"spotlight-card",{"description":1056,"icon":1057,"title":1058},"Check any track's certification status by ISRC, artist name, or cert number.","i-lucide-shield-check","Verify Tracks",[1054,1060],{"description":1061,"icon":1062,"title":1063},"Combine with mcp-metadata to read ISRC from files and verify automatically.","i-lucide-link","Multi-Agent Pipeline",[1054,1065],{"description":1066,"icon":1067,"title":1068},"The VHC registry is public. No API keys, no signup, no rate limits to worry about.","i-lucide-globe","No Auth Required",[863,1070,1072],{"id":1071},"setting-it-up","Setting it up",[1074,1075,1077],"h3",{"id":1076},"_1-clone-and-install","1. Clone and install",[1013,1079,1083],{"className":1080,"code":1081,"language":1082,"meta":728,"style":728},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","git clone https://github.com/musictechlab/mcp-verifiedhumancert.git\ncd mcp-verifiedhumancert\npoetry install\n","bash",[895,1084,1085,1101,1110],{"__ignoreMap":728},[1086,1087,1090,1094,1098],"span",{"class":1088,"line":1089},"line",1,[1086,1091,1093],{"class":1092},"sBMFI","git",[1086,1095,1097],{"class":1096},"sfazB"," clone",[1086,1099,1100],{"class":1096}," https://github.com/musictechlab/mcp-verifiedhumancert.git\n",[1086,1102,1103,1107],{"class":1088,"line":729},[1086,1104,1106],{"class":1105},"s2Zo4","cd",[1086,1108,1109],{"class":1096}," mcp-verifiedhumancert\n",[1086,1111,1113,1116],{"class":1088,"line":1112},3,[1086,1114,1115],{"class":1092},"poetry",[1086,1117,1118],{"class":1096}," install\n",[1074,1120,1122],{"id":1121},"_2-register-with-claude-code","2. Register with Claude Code",[842,1124,1125,1126,1129,1130,1133],{},"Add this to your ",[895,1127,1128],{},"~/.claude/settings.json"," or project ",[895,1131,1132],{},".claude/settings.local.json",":",[1013,1135,1140],{"className":1136,"code":1137,"filename":1138,"language":1139,"meta":728,"style":728},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"mcpServers\": {\n    \"vhc\": {\n      \"command\": \"poetry\",\n      \"args\": [\"--directory\", \"/path/to/mcp-verifiedhumancert\", \"run\", \"python\", \"-m\", \"mcp_verifiedhumancert\"]\n    }\n  }\n}\n","claude_mcp_config.json","json",[895,1141,1142,1148,1165,1179,1203,1274,1280,1286],{"__ignoreMap":728},[1086,1143,1144],{"class":1088,"line":1089},[1086,1145,1147],{"class":1146},"sMK4o","{\n",[1086,1149,1150,1153,1157,1160,1162],{"class":1088,"line":729},[1086,1151,1152],{"class":1146},"  \"",[1086,1154,1156],{"class":1155},"spNyl","mcpServers",[1086,1158,1159],{"class":1146},"\"",[1086,1161,1133],{"class":1146},[1086,1163,1164],{"class":1146}," {\n",[1086,1166,1167,1170,1173,1175,1177],{"class":1088,"line":1112},[1086,1168,1169],{"class":1146},"    \"",[1086,1171,1172],{"class":1092},"vhc",[1086,1174,1159],{"class":1146},[1086,1176,1133],{"class":1146},[1086,1178,1164],{"class":1146},[1086,1180,1182,1185,1189,1191,1193,1196,1198,1200],{"class":1088,"line":1181},4,[1086,1183,1184],{"class":1146},"      \"",[1086,1186,1188],{"class":1187},"sbssI","command",[1086,1190,1159],{"class":1146},[1086,1192,1133],{"class":1146},[1086,1194,1195],{"class":1146}," \"",[1086,1197,1115],{"class":1096},[1086,1199,1159],{"class":1146},[1086,1201,1202],{"class":1146},",\n",[1086,1204,1206,1208,1211,1213,1215,1218,1220,1223,1225,1228,1230,1233,1235,1237,1239,1242,1244,1246,1248,1251,1253,1255,1257,1260,1262,1264,1266,1269,1271],{"class":1088,"line":1205},5,[1086,1207,1184],{"class":1146},[1086,1209,1210],{"class":1187},"args",[1086,1212,1159],{"class":1146},[1086,1214,1133],{"class":1146},[1086,1216,1217],{"class":1146}," [",[1086,1219,1159],{"class":1146},[1086,1221,1222],{"class":1096},"--directory",[1086,1224,1159],{"class":1146},[1086,1226,1227],{"class":1146},",",[1086,1229,1195],{"class":1146},[1086,1231,1232],{"class":1096},"/path/to/mcp-verifiedhumancert",[1086,1234,1159],{"class":1146},[1086,1236,1227],{"class":1146},[1086,1238,1195],{"class":1146},[1086,1240,1241],{"class":1096},"run",[1086,1243,1159],{"class":1146},[1086,1245,1227],{"class":1146},[1086,1247,1195],{"class":1146},[1086,1249,1250],{"class":1096},"python",[1086,1252,1159],{"class":1146},[1086,1254,1227],{"class":1146},[1086,1256,1195],{"class":1146},[1086,1258,1259],{"class":1096},"-m",[1086,1261,1159],{"class":1146},[1086,1263,1227],{"class":1146},[1086,1265,1195],{"class":1146},[1086,1267,1268],{"class":1096},"mcp_verifiedhumancert",[1086,1270,1159],{"class":1146},[1086,1272,1273],{"class":1146},"]\n",[1086,1275,1277],{"class":1088,"line":1276},6,[1086,1278,1279],{"class":1146},"    }\n",[1086,1281,1283],{"class":1088,"line":1282},7,[1086,1284,1285],{"class":1146},"  }\n",[1086,1287,1289],{"class":1088,"line":1288},8,[1086,1290,1291],{"class":1146},"}\n",[1074,1293,1295],{"id":1294},"_3-start-using-it","3. Start using it",[842,1297,1298],{},"That's it. Ask Claude anything about the VHC registry:",[958,1300,1301,1306,1311],{},[961,1302,1303],{},[964,1304,1305],{},"\"Look up cert number VH-2026-000001\"",[961,1307,1308],{},[964,1309,1310],{},"\"What are the current VHC pricing tiers?\"",[961,1312,1313],{},[964,1314,1315],{},"\"How many tracks are certified on the platform?\"",[842,1317,1318],{},[1027,1319],{"alt":1320,"src":1321,"width":1322},"Looking up cert VH-2026-000001 in Claude Code","/images/blog/musictechlab_blog_vhc_mcp_cert_lookup.webp",600,[842,1324,1325],{},[1027,1326],{"alt":1327,"src":1328,"width":1322},"Querying VHC pricing tiers in Claude Code","/images/blog/musictechlab_blog_vhc_mcp_pricing.webp",[863,1330,1332],{"id":1331},"under-the-hood","Under the hood",[842,1334,1335,1336,1341],{},"The server is built with ",[846,1337,1340],{"href":1338,"rel":1339},"https://github.com/modelcontextprotocol/python-sdk",[850],"FastMCP",", the Python SDK for the Model Context Protocol. The architecture is straightforward:",[958,1343,1344,1352,1360],{},[961,1345,1346,1351],{},[996,1347,1348],{},[895,1349,1350],{},"client.py"," - a thin HTTP wrapper around the VHC REST API using httpx",[961,1353,1354,1359],{},[996,1355,1356],{},[895,1357,1358],{},"server.py"," - six tool definitions that call the client and return JSON",[961,1361,1362,1365],{},[996,1363,1364],{},"Tests"," - full test coverage using respx for HTTP mocking",[1013,1367,1370],{"className":1368,"code":1369,"filename":1358,"language":1250,"meta":728,"style":728},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","@mcp.tool()\ndef vhc_verify_isrc(isrc: str) -> str:\n    \"\"\"Verify a human-made music certification by ISRC code.\"\"\"\n    result = client.verify_by_isrc(isrc)\n    return json.dumps(result, indent=2, ensure_ascii=False)\n",[895,1371,1372,1388,1419,1432,1456],{"__ignoreMap":728},[1086,1373,1374,1377,1380,1382,1385],{"class":1088,"line":1089},[1086,1375,1376],{"class":1146},"@",[1086,1378,1379],{"class":1105},"mcp",[1086,1381,861],{"class":1146},[1086,1383,1384],{"class":1105},"tool",[1086,1386,1387],{"class":1146},"()\n",[1086,1389,1390,1393,1396,1399,1403,1405,1408,1411,1414,1416],{"class":1088,"line":729},[1086,1391,1392],{"class":1155},"def",[1086,1394,1395],{"class":1105}," vhc_verify_isrc",[1086,1397,1398],{"class":1146},"(",[1086,1400,1402],{"class":1401},"sHdIc","isrc",[1086,1404,1133],{"class":1146},[1086,1406,1407],{"class":1092}," str",[1086,1409,1410],{"class":1146},")",[1086,1412,1413],{"class":1146}," ->",[1086,1415,1407],{"class":1092},[1086,1417,1418],{"class":1146},":\n",[1086,1420,1421,1425,1429],{"class":1088,"line":1112},[1086,1422,1424],{"class":1423},"s7zQu","    \"\"\"",[1086,1426,1428],{"class":1427},"sHwdD","Verify a human-made music certification by ISRC code.",[1086,1430,1431],{"class":1423},"\"\"\"\n",[1086,1433,1434,1438,1441,1444,1446,1449,1451,1453],{"class":1088,"line":1181},[1086,1435,1437],{"class":1436},"sTEyZ","    result ",[1086,1439,1440],{"class":1146},"=",[1086,1442,1443],{"class":1436}," client",[1086,1445,861],{"class":1146},[1086,1447,1448],{"class":1105},"verify_by_isrc",[1086,1450,1398],{"class":1146},[1086,1452,1402],{"class":1105},[1086,1454,1455],{"class":1146},")\n",[1086,1457,1458,1461,1464,1466,1469,1471,1474,1476,1479,1481,1484,1486,1489],{"class":1088,"line":1205},[1086,1459,1460],{"class":1423},"    return",[1086,1462,1463],{"class":1436}," json",[1086,1465,861],{"class":1146},[1086,1467,1468],{"class":1105},"dumps",[1086,1470,1398],{"class":1146},[1086,1472,1473],{"class":1105},"result",[1086,1475,1227],{"class":1146},[1086,1477,1478],{"class":1401}," indent",[1086,1480,1440],{"class":1146},[1086,1482,1483],{"class":1187},"2",[1086,1485,1227],{"class":1146},[1086,1487,1488],{"class":1401}," ensure_ascii",[1086,1490,1491],{"class":1146},"=False)\n",[1013,1493,1495],{"className":1368,"code":1494,"filename":1350,"language":1250,"meta":728,"style":728},"def verify_by_isrc(isrc: str) -> dict:\n    \"\"\"Verify a certification by ISRC code.\"\"\"\n    return _get(\"/api/v1/verify\", params={\"isrc\": isrc})\n",[895,1496,1497,1521,1530],{"__ignoreMap":728},[1086,1498,1499,1501,1504,1506,1508,1510,1512,1514,1516,1519],{"class":1088,"line":1089},[1086,1500,1392],{"class":1155},[1086,1502,1503],{"class":1105}," verify_by_isrc",[1086,1505,1398],{"class":1146},[1086,1507,1402],{"class":1401},[1086,1509,1133],{"class":1146},[1086,1511,1407],{"class":1092},[1086,1513,1410],{"class":1146},[1086,1515,1413],{"class":1146},[1086,1517,1518],{"class":1092}," dict",[1086,1520,1418],{"class":1146},[1086,1522,1523,1525,1528],{"class":1088,"line":729},[1086,1524,1424],{"class":1423},[1086,1526,1527],{"class":1427},"Verify a certification by ISRC code.",[1086,1529,1431],{"class":1423},[1086,1531,1532,1534,1537,1539,1541,1544,1546,1548,1551,1554,1556,1558,1560,1562,1565],{"class":1088,"line":1112},[1086,1533,1460],{"class":1423},[1086,1535,1536],{"class":1105}," _get",[1086,1538,1398],{"class":1146},[1086,1540,1159],{"class":1146},[1086,1542,1543],{"class":1096},"/api/v1/verify",[1086,1545,1159],{"class":1146},[1086,1547,1227],{"class":1146},[1086,1549,1550],{"class":1401}," params",[1086,1552,1553],{"class":1146},"={",[1086,1555,1159],{"class":1146},[1086,1557,1402],{"class":1096},[1086,1559,1159],{"class":1146},[1086,1561,1133],{"class":1146},[1086,1563,1564],{"class":1105}," isrc",[1086,1566,1567],{"class":1146},"})\n",[842,1569,1570],{},"The entire server is under 200 lines of Python. That's intentional. MCP servers should be thin wrappers, not application frameworks.",[1572,1573,1574],"note",{},[842,1575,1576],{},"The project uses Poetry for dependency management, Ruff for linting and formatting, and pytest with respx for testing. CI runs on GitHub Actions.",[863,1578,1580],{"id":1579},"what-we-learned-building-mcp-servers","What we learned building MCP servers",[842,1582,1583,1584,1589,1590,1593],{},"This is the third MCP server we have open-sourced at MusicTech Lab (after ",[846,1585,1588],{"href":1586,"rel":1587},"https://github.com/musictechlab/signnow-mcp",[850],"signnow-mcp"," and ",[846,1591,988],{"href":986,"rel":1592},[850],"). A few patterns have emerged:",[991,1595,1596,1602,1608,1614],{},[961,1597,1598,1601],{},[996,1599,1600],{},"Keep servers focused."," One server per domain. Don't bundle unrelated tools into a single server.",[961,1603,1604,1607],{},[996,1605,1606],{},"Separate the client from the server."," The HTTP client should be testable independently of the MCP layer.",[961,1609,1610,1613],{},[996,1611,1612],{},"Return JSON, not prose."," Let Claude format the output for the user. The server's job is to provide structured data.",[961,1615,1616,1619,1620,1623],{},[996,1617,1618],{},"Skip authentication when you can."," Public APIs make MCP servers trivial to set up. No ",[895,1621,1622],{},".env"," files, no OAuth flows, no token management.",[863,1625,1627],{"id":1626},"open-source","Open source",[842,1629,1630,1631,1635],{},"The full source code is on GitHub: ",[846,1632,1634],{"href":858,"rel":1633},[850],"musictechlab/mcp-verifiedhumancert",". MIT licensed. Contributions welcome.",[842,1637,1638],{},"If you're building MCP servers for the music industry, or if you're using Verified Human Cert and want to integrate it into your tooling, we'd love to hear from you.",[863,1640,1642],{"id":1641},"related-resources","Related resources",[958,1644,1645,1652,1659,1665,1671],{},[961,1646,1647,1651],{},[846,1648,1650],{"href":858,"rel":1649},[850],"GitHub: musictechlab/mcp-verifiedhumancert"," - the source code",[961,1653,1654,1658],{},[846,1655,1657],{"href":986,"rel":1656},[850],"GitHub: musictechlab/mcp-metadata"," - audio metadata MCP server",[961,1660,1661,1664],{},[846,1662,1663],{"href":619},"SignNow MCP Server: E-Signatures from Claude Code"," - our previous MCP server article",[961,1666,1667,1670],{},[846,1668,851],{"href":848,"rel":1669},[850]," - the certification platform",[961,1672,1673,1678],{},[846,1674,1677],{"href":1675,"rel":1676},"https://modelcontextprotocol.io/",[850],"Model Context Protocol"," - the MCP standard",[1680,1681,1682],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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}",{"title":728,"searchDepth":729,"depth":729,"links":1684},[1685,1686,1687,1688,1693,1694,1695,1696],{"id":865,"depth":729,"text":866},{"id":979,"depth":729,"text":980},{"id":1039,"depth":729,"text":1040},{"id":1071,"depth":729,"text":1072,"children":1689},[1690,1691,1692],{"id":1076,"depth":1112,"text":1077},{"id":1121,"depth":1112,"text":1122},{"id":1294,"depth":1112,"text":1295},{"id":1331,"depth":729,"text":1332},{"id":1579,"depth":729,"text":1580},{"id":1626,"depth":729,"text":1627},{"id":1641,"depth":729,"text":1642},"2026-04-28T00:00:00.000Z","We open-sourced an MCP server that queries the Verified Human Cert registry. Verify human-made music certifications by ISRC, artist, track, or cert number directly from Claude Code.",[1700,1703,1706,1709,1712],{"question":1701,"answer":1702},"What is Verified Human Cert?","Verified Human Cert is a registry that certifies music tracks as human-made. Artists and labels register their tracks to receive proof that the music was created by humans, not generated by artificial intelligence.",{"question":1704,"answer":1705},"What is the mcp-verifiedhumancert server?","It is an open-source Model Context Protocol server that connects the Verified Human Cert public API to Claude Code and other MCP-compatible clients. It lets you verify certifications, browse the registry, and check pricing directly from your terminal.",{"question":1707,"answer":1708},"Do I need an API key to use this?","No. The server queries the public VHC API, which does not require authentication for read-only operations like verifying certifications and browsing the registry.",{"question":1710,"answer":1711},"What is the multi-agent workflow?","By combining mcp-verifiedhumancert with mcp-metadata, Claude can read the ISRC code embedded in an audio file and then verify its certification status in a single conversation. Two MCP servers, two agents, one orchestrator.",{"question":1713,"answer":1714},"Can I use this with clients other than Claude Code?","Yes. Any MCP-compatible client can use this server. The MCP protocol is open and supported by a growing number of tools.",12,{"src":1717,"credit":1718},"/images/blog/musictechlab_blog_verified_human_cert_mcp.webp","Photo by [Erika Giraud](https://unsplash.com/@erikasayssmile) on [Unsplash](https://unsplash.com/photos/JXA_BaeaCgM)",{"enabled":738,"items":1720},[1721,1724,1726],{"text":1722,"icon":1723},"The mcp-verifiedhumancert server exposes 6 tools for querying the Verified Human Cert registry.","i-lucide-terminal",{"text":1725,"icon":1062},"Combine with mcp-metadata to read ISRC from audio files and verify certifications automatically.",{"text":1727,"icon":1728},"No API key required. The server queries the public VHC registry at verifiedhumancert.com.","i-lucide-unlock",{},{"title":1731,"description":1732},"Verified Human Cert MCP Server | MusicTech Lab","Open-source MCP server for verifying human-made music certifications. Query by ISRC, artist, track, or cert number from Claude Code.",[1379,1626,1734,1735,1736,1737,1402],"verified-human-cert","music-certification","claude-code","multi-agent","qiI5dyjkEO9gWuMmxfqpQh8or1D6HZ9AxEMrcx-IDGQ",{"id":1740,"title":196,"authors":1741,"badge":1744,"body":1747,"category":731,"client":723,"date":3154,"description":3155,"extension":734,"faq":3156,"featured":69,"featuredOrder":723,"hidden":69,"image":3169,"keyTakeaways":3171,"meta":3182,"navigation":738,"path":197,"seo":3183,"status":723,"stem":198,"tags":3186,"teaser":723,"__hash__":3193},"posts/blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse.md",[1742],{"name":719,"to":720,"avatar":1743},{"src":722},{"label":1745,"color":1746},"Distribution","#0ea5e9",{"type":725,"value":1748,"toc":3128},[1749,1752,1755,1759,1762,1786,1789,1793,1797,1800,1806,1811,1828,1833,1844,1849,1853,1856,1861,1865,1882,1886,1900,1906,1910,1913,1918,1922,1939,1943,1957,1962,1966,2146,2150,2157,2161,2222,2227,2231,2234,2323,2327,2413,2418,2422,2428,2432,2435,2439,2547,2594,2598,2707,2752,2756,2844,2877,2882,2886,2889,2958,2962,2965,2976,2979,2990,2993,3004,3008,3011,3053,3056,3060,3063,3068,3073,3078,3081,3085,3088,3120,3125],[842,1750,1751],{},"Music companies sit on mountains of data. Streaming royalties from dozens of DSPs. Multi-currency transactions across territories. Financial records that need to reconcile between royalty systems and general ledgers. The database you choose to store and query this data shapes everything downstream: how fast your dashboards load, how much you pay per month, and whether your finance team can actually get answers without filing a ticket.",[842,1753,1754],{},"We have built music data systems on Elasticsearch, ClickHouse, and BigQuery. Each one solved a different problem. This article breaks down when to use which, with real pricing numbers and performance benchmarks so you can make the right call for your catalog.",[863,1756,1758],{"id":1757},"what-makes-music-data-different","What makes music data different",[842,1760,1761],{},"Before comparing databases, it helps to understand why music data is uniquely challenging. A typical mid-size label deals with several patterns that push general-purpose databases to their limits.",[1045,1763,1766,1771,1776,1781],{"className":1764},[1048,1049,1765,1051,1052],"md:grid-cols-2",[1054,1767],{"description":1768,"icon":1769,"title":1770},"A single quarterly DSP statement can contain millions of rows. One track, dozens of territories, multiple income types.","i-lucide-layers","High Volume",[1054,1772],{"description":1773,"icon":1774,"title":1775},"Revenue arrives in GBP, gets converted to USD, reported in EUR, and settled in the artist's local currency.","i-lucide-coins","Multi-Currency",[1054,1777],{"description":1778,"icon":1779,"title":1780},"Track to release to label to artist to contract. Each level has its own royalty rates, reserves, and deductions.","i-lucide-git-branch","Complex Relationships",[1054,1782],{"description":1783,"icon":1784,"title":1785},"Every transaction has two dates: when the DSP reported it (cash basis) and when the stream actually happened (accrual basis).","i-lucide-calendar-range","Dual Temporal",[842,1787,1788],{},"These patterns mean your database needs to handle complex joins, large aggregations, and flexible time-based queries. Not every database does this equally well.",[863,1790,1792],{"id":1791},"the-three-contenders","The three contenders",[1074,1794,1796],{"id":1795},"bigquery-the-serverless-warehouse","BigQuery: the serverless warehouse",[842,1798,1799],{},"Google BigQuery is a fully managed, serverless data warehouse. You do not provision clusters, tune nodes, or worry about scaling. You write SQL, BigQuery figures out the rest.",[842,1801,1802,1805],{},[996,1803,1804],{},"Architecture:"," Columnar storage with a distributed query engine. Separates storage and compute, so you pay independently for each. Data lives in Google Cloud Storage under the hood.",[842,1807,1808],{},[996,1809,1810],{},"What it does well for music companies:",[958,1812,1813,1816,1819,1822,1825],{},[961,1814,1815],{},"Complex SQL joins across 15+ dimension tables (labels, artists, tracks, territories, DSPs)",[961,1817,1818],{},"dbt integration is first-class, with native support for the staging-intermediate-marts pattern",[961,1820,1821],{},"Handles 50M+ row fact tables without breaking a sweat",[961,1823,1824],{},"Native connectors to Looker, Sigma Computing, and Looker Studio for BI",[961,1826,1827],{},"Zero infrastructure management, your finance team never waits on DevOps",[842,1829,1830],{},[996,1831,1832],{},"Where it falls short:",[958,1834,1835,1838,1841],{},[961,1836,1837],{},"Query latency is typically 1-2 seconds, not sub-second",[961,1839,1840],{},"On-demand pricing can spike if analysts run unoptimized queries on large tables",[961,1842,1843],{},"Not ideal for high-concurrency, user-facing dashboards with hundreds of simultaneous users",[1032,1845,1846],{},[842,1847,1848],{},"BigQuery offers a 1 TB/month free tier for queries. For a small label just getting started with analytics, you can run a meaningful data warehouse for nearly zero cost.",[1074,1850,1852],{"id":1851},"redshift-the-aws-powerhouse","Redshift: the AWS powerhouse",[842,1854,1855],{},"Amazon Redshift is a cluster-based columnar data warehouse built on PostgreSQL. It has been the default choice for AWS-native organizations for over a decade.",[842,1857,1858,1860],{},[996,1859,1804],{}," Provisioned clusters with leader and compute nodes. You choose node types and quantities based on your workload. Redshift Serverless is also available as a pay-per-query alternative.",[842,1862,1863],{},[996,1864,1810],{},[958,1866,1867,1870,1873,1876,1879],{},[961,1868,1869],{},"Deep AWS ecosystem integration (S3, Glue, Lambda, Step Functions)",[961,1871,1872],{},"PostgreSQL compatibility means most SQL tools and libraries work out of the box",[961,1874,1875],{},"Mature ecosystem with extensive documentation and community support",[961,1877,1878],{},"Fine-grained cluster tuning for teams that want control over performance",[961,1880,1881],{},"Spectrum lets you query data directly in S3 without loading it",[842,1883,1884],{},[996,1885,1832],{},[958,1887,1888,1891,1894,1897],{},[961,1889,1890],{},"Provisioned clusters cost money even when idle, a 24/7 dc2.large node runs ~$180/month",[961,1892,1893],{},"Requires capacity planning and occasional cluster resizing",[961,1895,1896],{},"More operational overhead than BigQuery",[961,1898,1899],{},"Scaling up means migrating to larger nodes or adding more, not automatic",[1901,1902,1903],"warning",{},[842,1904,1905],{},"With Redshift provisioned clusters, you pay for compute whether anyone is running queries or not. If your team primarily queries during business hours, you are paying for 16 idle hours per day. Consider Redshift Serverless or BigQuery if your usage is bursty.",[1074,1907,1909],{"id":1908},"clickhouse-the-speed-demon","ClickHouse: the speed demon",[842,1911,1912],{},"ClickHouse is an open-source, column-oriented OLAP database designed for sub-second analytical queries. Originally built at Yandex to handle billions of page-view events, it has become the go-to choice for real-time analytics.",[842,1914,1915,1917],{},[996,1916,1804],{}," Vectorized query execution with native compression. Processes data in columns rather than rows, which makes aggregations extremely fast. Available as self-hosted (open source) or ClickHouse Cloud (managed).",[842,1919,1920],{},[996,1921,1810],{},[958,1923,1924,1927,1930,1933,1936],{},[961,1925,1926],{},"Sub-second queries on billions of rows, 5-10x faster than BigQuery and Redshift on aggregations",[961,1928,1929],{},"Real-time ingestion with no batch delay",[961,1931,1932],{},"Excellent compression ratios reduce storage costs significantly",[961,1934,1935],{},"Ideal for streaming play-count dashboards, listener behavior analytics, and DSP performance monitoring",[961,1937,1938],{},"Cost-effective at scale, especially self-hosted",[842,1940,1941],{},[996,1942,1832],{},[958,1944,1945,1948,1951,1954],{},[961,1946,1947],{},"Joins are more limited and less optimized than BigQuery or Redshift",[961,1949,1950],{},"Smaller BI tool ecosystem, Looker and Sigma have limited native support",[961,1952,1953],{},"Self-hosting requires operational expertise (replication, backups, upgrades)",[961,1955,1956],{},"Not designed for complex multi-table financial reporting",[1032,1958,1959],{},[842,1960,1961],{},"ClickHouse compresses data aggressively, often achieving 5-10x compression. A 500 GB dataset in BigQuery might only consume 50-100 GB in ClickHouse, directly reducing storage costs.",[863,1963,1965],{"id":1964},"head-to-head-comparison","Head-to-head comparison",[871,1967,1968,1984],{},[874,1969,1970],{},[877,1971,1972,1975,1978,1981],{},[880,1973,1974],{},"Feature",[880,1976,1977],{},"BigQuery",[880,1979,1980],{},"Redshift",[880,1982,1983],{},"ClickHouse",[887,1985,1986,2002,2018,2034,2050,2066,2082,2098,2114,2130],{},[877,1987,1988,1993,1996,1999],{},[892,1989,1990],{},[996,1991,1992],{},"Deployment",[892,1994,1995],{},"Serverless (managed)",[892,1997,1998],{},"Cluster or Serverless",[892,2000,2001],{},"Self-hosted or Cloud",[877,2003,2004,2009,2012,2015],{},[892,2005,2006],{},[996,2007,2008],{},"Query latency",[892,2010,2011],{},"1-2 seconds",[892,2013,2014],{},"1-3 seconds",[892,2016,2017],{},"50-500 ms",[877,2019,2020,2025,2028,2031],{},[892,2021,2022],{},[996,2023,2024],{},"Joins",[892,2026,2027],{},"Full SQL, excellent",[892,2029,2030],{},"Full SQL, good",[892,2032,2033],{},"Limited, basic",[877,2035,2036,2041,2044,2047],{},[892,2037,2038],{},[996,2039,2040],{},"Scaling",[892,2042,2043],{},"Automatic",[892,2045,2046],{},"Manual (cluster) or auto (serverless)",[892,2048,2049],{},"Manual (self) or auto (cloud)",[877,2051,2052,2057,2060,2063],{},[892,2053,2054],{},[996,2055,2056],{},"dbt support",[892,2058,2059],{},"Native, first-class",[892,2061,2062],{},"Native, good",[892,2064,2065],{},"Community adapter",[877,2067,2068,2073,2076,2079],{},[892,2069,2070],{},[996,2071,2072],{},"BI ecosystem",[892,2074,2075],{},"Looker, Sigma, Tableau, Looker Studio",[892,2077,2078],{},"QuickSight, Tableau, Looker",[892,2080,2081],{},"Grafana, Metabase, Superset",[877,2083,2084,2089,2092,2095],{},[892,2085,2086],{},[996,2087,2088],{},"SQL dialect",[892,2090,2091],{},"GoogleSQL",[892,2093,2094],{},"PostgreSQL-based",[892,2096,2097],{},"ClickHouse SQL",[877,2099,2100,2105,2108,2111],{},[892,2101,2102],{},[996,2103,2104],{},"Best concurrency",[892,2106,2107],{},"~100 concurrent",[892,2109,2110],{},"~50 concurrent",[892,2112,2113],{},"1000+ concurrent",[877,2115,2116,2121,2124,2127],{},[892,2117,2118],{},[996,2119,2120],{},"Learning curve",[892,2122,2123],{},"Low",[892,2125,2126],{},"Medium",[892,2128,2129],{},"Medium-High",[877,2131,2132,2137,2140,2143],{},[892,2133,2134],{},[996,2135,2136],{},"Vendor lock-in",[892,2138,2139],{},"High (GCP)",[892,2141,2142],{},"High (AWS)",[892,2144,2145],{},"Low (open source)",[863,2147,2149],{"id":2148},"pricing-simulation-a-mid-size-music-label","Pricing simulation: a mid-size music label",[842,2151,2152,2153,2156],{},"Let's model real costs for a label with ",[996,2154,2155],{},"15 owned labels, 50 million royalty rows, and a 5-person analytics team"," running daily queries.",[1074,2158,2160],{"id":2159},"storage-50m-rows-of-royalty-data-200-gb-uncompressed","Storage: 50M rows of royalty data (~200 GB uncompressed)",[871,2162,2163,2176],{},[874,2164,2165],{},[877,2166,2167,2170,2173],{},[880,2168,2169],{},"Database",[880,2171,2172],{},"Storage model",[880,2174,2175],{},"Monthly cost",[887,2177,2178,2193,2207],{},[877,2179,2180,2184,2187],{},[892,2181,2182],{},[996,2183,1977],{},[892,2185,2186],{},"$20/TB active, $10/TB long-term",[892,2188,2189,2192],{},[996,2190,2191],{},"$4/mo"," (200 GB active)",[877,2194,2195,2199,2202],{},[892,2196,2197],{},[996,2198,1980],{},[892,2200,2201],{},"Included in node cost (managed storage: $0.024/GB)",[892,2203,2204],{},[996,2205,2206],{},"$4.80/mo",[877,2208,2209,2214,2217],{},[892,2210,2211],{},[996,2212,2213],{},"ClickHouse Cloud",[892,2215,2216],{},"$25.30/TB (compressed, ~40 GB after compression)",[892,2218,2219],{},[996,2220,2221],{},"$1.01/mo",[1572,2223,2224],{},[842,2225,2226],{},"Storage costs are nearly identical and trivially small at this scale. The real cost difference comes from compute.",[1074,2228,2230],{"id":2229},"compute-daily-queries-by-a-5-person-team","Compute: daily queries by a 5-person team",[842,2232,2233],{},"Assumptions: 20 queries/day per analyst, ~5 GB scanned per query, 22 working days/month.",[871,2235,2236,2247],{},[874,2237,2238],{},[877,2239,2240,2242,2245],{},[880,2241,2169],{},[880,2243,2244],{},"Pricing model",[880,2246,2175],{},[887,2248,2249,2264,2279,2294,2309],{},[877,2250,2251,2256,2259],{},[892,2252,2253,2255],{},[996,2254,1977],{}," (on-demand)",[892,2257,2258],{},"$6.25/TB scanned. 5 users x 20 queries x 5 GB x 22 days = ~11 TB/month",[892,2260,2261],{},[996,2262,2263],{},"$68/mo",[877,2265,2266,2271,2274],{},[892,2267,2268,2270],{},[996,2269,1977],{}," (flat-rate)",[892,2272,2273],{},"100 slots at ~$0.04/slot-hour x 730 hours",[892,2275,2276],{},[996,2277,2278],{},"$2,920/mo",[877,2280,2281,2286,2289],{},[892,2282,2283,2285],{},[996,2284,1980],{}," (dc2.large, 2 nodes)",[892,2287,2288],{},"$0.25/node/hour x 2 nodes x 730 hours",[892,2290,2291],{},[996,2292,2293],{},"$365/mo",[877,2295,2296,2301,2304],{},[892,2297,2298,2300],{},[996,2299,1980],{}," (Serverless)",[892,2302,2303],{},"$0.375/RPU-hour, ~4 RPUs x ~8 active hours x 22 days",[892,2305,2306],{},[996,2307,2308],{},"$264/mo",[877,2310,2311,2315,2318],{},[892,2312,2313],{},[996,2314,2213],{},[892,2316,2317],{},"~$0.10/compute-unit, auto-scaling with idle pause",[892,2319,2320],{},[996,2321,2322],{},"$80-150/mo",[1074,2324,2326],{"id":2325},"total-monthly-cost-estimate","Total monthly cost estimate",[871,2328,2329,2341],{},[874,2330,2331],{},[877,2332,2333,2335,2338],{},[880,2334,2169],{},[880,2336,2337],{},"Configuration",[880,2339,2340],{},"Total/month",[887,2342,2343,2357,2371,2385,2399],{},[877,2344,2345,2349,2352],{},[892,2346,2347],{},[996,2348,1977],{},[892,2350,2351],{},"On-demand",[892,2353,2354],{},[996,2355,2356],{},"~$72",[877,2358,2359,2363,2366],{},[892,2360,2361],{},[996,2362,1977],{},[892,2364,2365],{},"Flat-rate (100 slots)",[892,2367,2368],{},[996,2369,2370],{},"~$2,925",[877,2372,2373,2377,2380],{},[892,2374,2375],{},[996,2376,1980],{},[892,2378,2379],{},"2x dc2.large (always on)",[892,2381,2382],{},[996,2383,2384],{},"~$370",[877,2386,2387,2391,2394],{},[892,2388,2389],{},[996,2390,1980],{},[892,2392,2393],{},"Serverless",[892,2395,2396],{},[996,2397,2398],{},"~$269",[877,2400,2401,2405,2408],{},[892,2402,2403],{},[996,2404,2213],{},[892,2406,2407],{},"Dev tier with auto-pause",[892,2409,2410],{},[996,2411,2412],{},"~$100-150",[1901,2414,2415],{},[842,2416,2417],{},"BigQuery on-demand looks cheapest, but costs scale linearly with query volume. If your team grows to 20 analysts running complex queries, you could easily hit $500-1,000/month. Partition your tables and use query cost controls to prevent surprises.",[1074,2419,2421],{"id":2420},"annual-cost-projection","Annual cost projection",[1013,2423,2426],{"className":2424,"code":2425,"language":1018,"meta":728},[1016],"                        Year 1      Year 2      Year 3\n                        (50M rows)  (65M rows)  (85M rows)\nBigQuery (on-demand)    $864        $1,080      $1,350\nRedshift (2x dc2)      $4,440      $4,440      $6,660*\nRedshift Serverless     $3,228      $3,900      $4,800\nClickHouse Cloud        $1,500      $1,800      $2,400\n\n* Redshift cluster upgrade needed at ~70M rows\n",[895,2427,2425],{"__ignoreMap":728},[863,2429,2431],{"id":2430},"performance-benchmarks-music-data-queries","Performance benchmarks: music data queries",[842,2433,2434],{},"Here is how each database handles typical music industry analytical queries on a 50M-row royalty dataset.",[1074,2436,2438],{"id":2437},"query-1-revenue-by-label-last-12-months","Query 1: Revenue by label, last 12 months",[1013,2440,2444],{"className":2441,"code":2442,"language":2443,"meta":728,"style":728},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","SELECT label_name, SUM(net_revenue) AS total_revenue\nFROM fact_revenue\nJOIN dim_label USING (label_key)\nJOIN dim_date USING (date_key)\nWHERE dim_date.date >= DATE_SUB(CURRENT_DATE(), INTERVAL 12 MONTH)\nGROUP BY label_name\nORDER BY total_revenue DESC\n","sql",[895,2445,2446,2466,2474,2488,2500,2528,2536],{"__ignoreMap":728},[1086,2447,2448,2451,2454,2457,2460,2463],{"class":1088,"line":1089},[1086,2449,2450],{"class":1187},"SELECT",[1086,2452,2453],{"class":1436}," label_name, ",[1086,2455,2456],{"class":1105},"SUM",[1086,2458,2459],{"class":1436},"(net_revenue) ",[1086,2461,2462],{"class":1187},"AS",[1086,2464,2465],{"class":1436}," total_revenue\n",[1086,2467,2468,2471],{"class":1088,"line":729},[1086,2469,2470],{"class":1187},"FROM",[1086,2472,2473],{"class":1436}," fact_revenue\n",[1086,2475,2476,2479,2482,2485],{"class":1088,"line":1112},[1086,2477,2478],{"class":1187},"JOIN",[1086,2480,2481],{"class":1436}," dim_label ",[1086,2483,2484],{"class":1187},"USING",[1086,2486,2487],{"class":1436}," (label_key)\n",[1086,2489,2490,2492,2495,2497],{"class":1088,"line":1181},[1086,2491,2478],{"class":1187},[1086,2493,2494],{"class":1436}," dim_date ",[1086,2496,2484],{"class":1187},[1086,2498,2499],{"class":1436}," (date_key)\n",[1086,2501,2502,2505,2508,2511,2514,2517,2520,2523,2526],{"class":1088,"line":1205},[1086,2503,2504],{"class":1187},"WHERE",[1086,2506,2507],{"class":1436}," dim_date.date ",[1086,2509,2510],{"class":1146},">=",[1086,2512,2513],{"class":1436}," DATE_SUB(CURRENT_DATE",[1086,2515,2516],{"class":1146},"()",[1086,2518,2519],{"class":1436},", INTERVAL ",[1086,2521,2522],{"class":1187},"12",[1086,2524,2525],{"class":1187}," MONTH",[1086,2527,1455],{"class":1436},[1086,2529,2530,2533],{"class":1088,"line":1276},[1086,2531,2532],{"class":1187},"GROUP BY",[1086,2534,2535],{"class":1436}," label_name\n",[1086,2537,2538,2541,2544],{"class":1088,"line":1282},[1086,2539,2540],{"class":1187},"ORDER BY",[1086,2542,2543],{"class":1436}," total_revenue ",[1086,2545,2546],{"class":1187},"DESC\n",[871,2548,2549,2561],{},[874,2550,2551],{},[877,2552,2553,2555,2558],{},[880,2554,2169],{},[880,2556,2557],{},"Execution time",[880,2559,2560],{},"Data scanned",[887,2562,2563,2573,2584],{},[877,2564,2565,2567,2570],{},[892,2566,1977],{},[892,2568,2569],{},"1.2 seconds",[892,2571,2572],{},"3.8 GB",[877,2574,2575,2578,2581],{},[892,2576,2577],{},"Redshift (dc2.large x2)",[892,2579,2580],{},"1.8 seconds",[892,2582,2583],{},"Full table",[877,2585,2586,2588,2591],{},[892,2587,1983],{},[892,2589,2590],{},"87 ms",[892,2592,2593],{},"0.4 GB (compressed)",[1074,2595,2597],{"id":2596},"query-2-artist-recoupment-status-across-all-contracts","Query 2: Artist recoupment status across all contracts",[1013,2599,2601],{"className":2441,"code":2600,"language":2443,"meta":728,"style":728},"SELECT artist_name, contract_id,\n       SUM(revenue) AS earned,\n       advance_amount,\n       advance_amount - SUM(revenue) AS balance\nFROM fact_revenue\nJOIN dim_artist USING (artist_key)\nJOIN dim_contract USING (contract_key)\nGROUP BY artist_name, contract_id, advance_amount\nHAVING balance > 0\nORDER BY balance DESC\n",[895,2602,2603,2610,2623,2628,2646,2652,2664,2676,2683,2698],{"__ignoreMap":728},[1086,2604,2605,2607],{"class":1088,"line":1089},[1086,2606,2450],{"class":1187},[1086,2608,2609],{"class":1436}," artist_name, contract_id,\n",[1086,2611,2612,2615,2618,2620],{"class":1088,"line":729},[1086,2613,2614],{"class":1105},"       SUM",[1086,2616,2617],{"class":1436},"(revenue) ",[1086,2619,2462],{"class":1187},[1086,2621,2622],{"class":1436}," earned,\n",[1086,2624,2625],{"class":1088,"line":1112},[1086,2626,2627],{"class":1436},"       advance_amount,\n",[1086,2629,2630,2633,2636,2639,2641,2643],{"class":1088,"line":1181},[1086,2631,2632],{"class":1436},"       advance_amount ",[1086,2634,2635],{"class":1146},"-",[1086,2637,2638],{"class":1105}," SUM",[1086,2640,2617],{"class":1436},[1086,2642,2462],{"class":1187},[1086,2644,2645],{"class":1436}," balance\n",[1086,2647,2648,2650],{"class":1088,"line":1205},[1086,2649,2470],{"class":1187},[1086,2651,2473],{"class":1436},[1086,2653,2654,2656,2659,2661],{"class":1088,"line":1276},[1086,2655,2478],{"class":1187},[1086,2657,2658],{"class":1436}," dim_artist ",[1086,2660,2484],{"class":1187},[1086,2662,2663],{"class":1436}," (artist_key)\n",[1086,2665,2666,2668,2671,2673],{"class":1088,"line":1282},[1086,2667,2478],{"class":1187},[1086,2669,2670],{"class":1436}," dim_contract ",[1086,2672,2484],{"class":1187},[1086,2674,2675],{"class":1436}," (contract_key)\n",[1086,2677,2678,2680],{"class":1088,"line":1288},[1086,2679,2532],{"class":1187},[1086,2681,2682],{"class":1436}," artist_name, contract_id, advance_amount\n",[1086,2684,2686,2689,2692,2695],{"class":1088,"line":2685},9,[1086,2687,2688],{"class":1187},"HAVING",[1086,2690,2691],{"class":1436}," balance ",[1086,2693,2694],{"class":1146},">",[1086,2696,2697],{"class":1187}," 0\n",[1086,2699,2701,2703,2705],{"class":1088,"line":2700},10,[1086,2702,2540],{"class":1187},[1086,2704,2691],{"class":1436},[1086,2706,2546],{"class":1187},[871,2708,2709,2720],{},[874,2710,2711],{},[877,2712,2713,2715,2717],{},[880,2714,2169],{},[880,2716,2557],{},[880,2718,2719],{},"Notes",[887,2721,2722,2732,2742],{},[877,2723,2724,2726,2729],{},[892,2725,1977],{},[892,2727,2728],{},"2.1 seconds",[892,2730,2731],{},"Handles multi-table join well",[877,2733,2734,2736,2739],{},[892,2735,1980],{},[892,2737,2738],{},"2.8 seconds",[892,2740,2741],{},"Comparable with sort keys",[877,2743,2744,2746,2749],{},[892,2745,1983],{},[892,2747,2748],{},"450 ms",[892,2750,2751],{},"Slower due to joins, still fast",[1074,2753,2755],{"id":2754},"query-3-real-time-play-counts-by-territory-1b-events","Query 3: Real-time play counts by territory (1B events)",[1013,2757,2759],{"className":2441,"code":2758,"language":2443,"meta":728,"style":728},"SELECT territory, COUNT(*) AS plays, SUM(revenue) AS revenue\nFROM streaming_events\nWHERE event_date >= today() - 7\nGROUP BY territory\nORDER BY plays DESC\nLIMIT 50\n",[895,2760,2761,2793,2800,2820,2827,2836],{"__ignoreMap":728},[1086,2762,2763,2765,2768,2771,2773,2776,2779,2781,2784,2786,2788,2790],{"class":1088,"line":1089},[1086,2764,2450],{"class":1187},[1086,2766,2767],{"class":1436}," territory, ",[1086,2769,2770],{"class":1105},"COUNT",[1086,2772,1398],{"class":1436},[1086,2774,2775],{"class":1146},"*",[1086,2777,2778],{"class":1436},") ",[1086,2780,2462],{"class":1187},[1086,2782,2783],{"class":1436}," plays, ",[1086,2785,2456],{"class":1105},[1086,2787,2617],{"class":1436},[1086,2789,2462],{"class":1187},[1086,2791,2792],{"class":1436}," revenue\n",[1086,2794,2795,2797],{"class":1088,"line":729},[1086,2796,2470],{"class":1187},[1086,2798,2799],{"class":1436}," streaming_events\n",[1086,2801,2802,2804,2807,2809,2812,2814,2817],{"class":1088,"line":1112},[1086,2803,2504],{"class":1187},[1086,2805,2806],{"class":1436}," event_date ",[1086,2808,2510],{"class":1146},[1086,2810,2811],{"class":1436}," today",[1086,2813,2516],{"class":1146},[1086,2815,2816],{"class":1146}," -",[1086,2818,2819],{"class":1187}," 7\n",[1086,2821,2822,2824],{"class":1088,"line":1181},[1086,2823,2532],{"class":1187},[1086,2825,2826],{"class":1436}," territory\n",[1086,2828,2829,2831,2834],{"class":1088,"line":1205},[1086,2830,2540],{"class":1187},[1086,2832,2833],{"class":1436}," plays ",[1086,2835,2546],{"class":1187},[1086,2837,2838,2841],{"class":1088,"line":1276},[1086,2839,2840],{"class":1187},"LIMIT",[1086,2842,2843],{"class":1187}," 50\n",[871,2845,2846,2854],{},[874,2847,2848],{},[877,2849,2850,2852],{},[880,2851,2169],{},[880,2853,2557],{},[887,2855,2856,2863,2870],{},[877,2857,2858,2860],{},[892,2859,1977],{},[892,2861,2862],{},"3.4 seconds",[877,2864,2865,2867],{},[892,2866,1980],{},[892,2868,2869],{},"4.1 seconds",[877,2871,2872,2874],{},[892,2873,1983],{},[892,2875,2876],{},"120 ms",[1572,2878,2879],{},[842,2880,2881],{},"ClickHouse dominates on single-table aggregations. BigQuery and Redshift are better when you need complex joins across many dimension tables, which is the typical pattern for financial reporting.",[863,2883,2885],{"id":2884},"decision-framework","Decision framework",[842,2887,2888],{},"Choosing the right database is not about which one is \"best.\" It is about which one fits your specific workload, team, and infrastructure.",[1045,2890,2892,2914,2936],{"className":2891},[1048,1049,1050,1051,1052],[1054,2893,2897],{"description":2894,"icon":2895,"title":2896},"Financial reporting, royalty reconciliation, small teams.","i-lucide-database","Choose BigQuery",[958,2898,2899,2902,2905,2908,2911],{},[961,2900,2901],{},"Reconciling royalty systems to GL",[961,2903,2904],{},"Executive dashboards with complex joins",[961,2906,2907],{},"dbt-powered transformation pipelines",[961,2909,2910],{},"Team has no dedicated DevOps",[961,2912,2913],{},"Already on Google Cloud",[1054,2915,2919],{"description":2916,"icon":2917,"title":2918},"AWS-native orgs with existing data engineering teams.","i-lucide-cloud","Choose Redshift",[958,2920,2921,2924,2927,2930,2933],{},[961,2922,2923],{},"Deep AWS ecosystem (S3, Glue, Lambda)",[961,2925,2926],{},"Team already knows PostgreSQL",[961,2928,2929],{},"Need fine-grained performance tuning",[961,2931,2932],{},"Running other AWS analytics services",[961,2934,2935],{},"Want Spectrum for S3 data lake queries",[1054,2937,2941],{"description":2938,"icon":2939,"title":2940},"Real-time analytics, high-concurrency dashboards.","i-lucide-zap","Choose ClickHouse",[958,2942,2943,2946,2949,2952,2955],{},[961,2944,2945],{},"Streaming play-count dashboards",[961,2947,2948],{},"Listener behavior analytics",[961,2950,2951],{},"DSP performance monitoring",[961,2953,2954],{},"User-facing analytics features",[961,2956,2957],{},"Need sub-second query response",[1074,2959,2961],{"id":2960},"can-you-combine-them","Can you combine them?",[842,2963,2964],{},"Yes, and some of the most effective music data architectures do exactly that.",[842,2966,2967,2968,2971,2972,2975],{},"A practical combination: ",[996,2969,2970],{},"ClickHouse for real-time dashboards"," (streaming counts, live territory maps, DSP performance) feeding from event streams, plus ",[996,2973,2974],{},"BigQuery for financial reporting"," (royalty reconciliation, label P&Ls, catalog valuation) with dbt transformations on a daily batch cycle.",[842,2977,2978],{},"This works well when:",[958,2980,2981,2984,2987],{},[961,2982,2983],{},"Different teams have different latency requirements",[961,2985,2986],{},"Real-time event data and financial reporting serve different audiences",[961,2988,2989],{},"Your streaming volume justifies a dedicated OLAP engine",[842,2991,2992],{},"It becomes over-engineering when:",[958,2994,2995,2998,3001],{},[961,2996,2997],{},"Your team is small (under 5 people)",[961,2999,3000],{},"All queries are batch/daily",[961,3002,3003],{},"You do not have dedicated infrastructure engineers",[863,3005,3007],{"id":3006},"what-about-elasticsearch-and-dynamodb","What about Elasticsearch and DynamoDB?",[842,3009,3010],{},"These come up in conversations about music data, but they solve fundamentally different problems.",[871,3012,3013,3025],{},[874,3014,3015],{},[877,3016,3017,3019,3022],{},[880,3018,2169],{},[880,3020,3021],{},"Purpose",[880,3023,3024],{},"Music use case",[887,3026,3027,3040],{},[877,3028,3029,3034,3037],{},[892,3030,3031],{},[996,3032,3033],{},"Elasticsearch",[892,3035,3036],{},"Full-text search and log analytics",[892,3038,3039],{},"Catalog search (find tracks by title, ISRC, artist), log monitoring, operational dashboards",[877,3041,3042,3047,3050],{},[892,3043,3044],{},[996,3045,3046],{},"DynamoDB",[892,3048,3049],{},"Transactional key-value store",[892,3051,3052],{},"User accounts, playlist storage, session management, low-latency app backends",[842,3054,3055],{},"Neither is a data warehouse. You would not run royalty reconciliation on Elasticsearch or catalog valuation queries on DynamoDB. Know the difference, and use each tool in its lane.",[863,3057,3059],{"id":3058},"our-experience-at-musictech-lab","Our experience at MusicTech Lab",[842,3061,3062],{},"We have built music data systems across this entire spectrum.",[842,3064,3065,3067],{},[996,3066,3033],{}," powered our royalty search engine, processing data from 15+ distributors into a unified, searchable catalog. It excels at finding a specific transaction across 200 million records in milliseconds. But it cannot calculate a label P&L.",[842,3069,3070,3072],{},[996,3071,1983],{}," drives our AI-powered analytics dashboard, where non-technical users type plain English questions and get charts back in seconds. Sub-second aggregation on tens of millions of rows makes the conversational experience feel instant.",[842,3074,3075,3077],{},[996,3076,1977],{}," is the direction we are actively exploring for financial reporting and reconciliation, connecting royalty data to general ledger systems through dbt transformations. We are deep in the learning phase right now, studying dimensional modeling, star schemas, and the dbt ecosystem. The serverless model and native BI integrations make it a compelling foundation for label finance teams.",[842,3079,3080],{},"The lesson: there is no single \"best database for music.\" There is the right database for your specific workload. And sometimes the right answer is to start learning the tool before your next project demands it.",[863,3082,3084],{"id":3083},"getting-started","Getting started",[842,3086,3087],{},"If you are a music company evaluating databases for the first time, start here:",[991,3089,3090,3096,3102,3108,3114],{},[961,3091,3092,3095],{},[996,3093,3094],{},"Map your workloads."," List every report, dashboard, and query your team runs. Categorize each as financial reporting, real-time analytics, or search.",[961,3097,3098,3101],{},[996,3099,3100],{},"Count your data."," How many rows today? What is your annual growth rate? A 10M-row label has very different needs than a 500M-row distributor.",[961,3103,3104,3107],{},[996,3105,3106],{},"Assess your team."," Do you have data engineers? DevOps? Or is your \"data team\" a finance analyst who knows Excel? This changes the answer dramatically.",[961,3109,3110,3113],{},[996,3111,3112],{},"Start serverless."," Unless you have a clear reason for provisioned infrastructure, BigQuery on-demand or ClickHouse Cloud with auto-pause will get you running faster and cheaper.",[961,3115,3116,3119],{},[996,3117,3118],{},"Design for the dimensional model first."," The database choice matters less than your data model. A well-designed star schema performs well on any of these three platforms. A poorly designed model performs badly on all of them.",[1032,3121,3122],{},[842,3123,3124],{},"The most expensive database decision is not picking the wrong vendor. It is building without a proper data model and having to rebuild everything six months later. Invest the time upfront in your dimensional design.",[1680,3126,3127],{},"html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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);}",{"title":728,"searchDepth":729,"depth":729,"links":3129},[3130,3131,3136,3137,3143,3148,3151,3152,3153],{"id":1757,"depth":729,"text":1758},{"id":1791,"depth":729,"text":1792,"children":3132},[3133,3134,3135],{"id":1795,"depth":1112,"text":1796},{"id":1851,"depth":1112,"text":1852},{"id":1908,"depth":1112,"text":1909},{"id":1964,"depth":729,"text":1965},{"id":2148,"depth":729,"text":2149,"children":3138},[3139,3140,3141,3142],{"id":2159,"depth":1112,"text":2160},{"id":2229,"depth":1112,"text":2230},{"id":2325,"depth":1112,"text":2326},{"id":2420,"depth":1112,"text":2421},{"id":2430,"depth":729,"text":2431,"children":3144},[3145,3146,3147],{"id":2437,"depth":1112,"text":2438},{"id":2596,"depth":1112,"text":2597},{"id":2754,"depth":1112,"text":2755},{"id":2884,"depth":729,"text":2885,"children":3149},[3150],{"id":2960,"depth":1112,"text":2961},{"id":3006,"depth":729,"text":3007},{"id":3058,"depth":729,"text":3059},{"id":3083,"depth":729,"text":3084},"2026-04-23T00:00:00.000Z","A practical comparison of BigQuery, Redshift, and ClickHouse for music industry workloads, from royalty reporting to real-time streaming analytics.",[3157,3160,3163,3166],{"question":3158,"answer":3159},"Which database is best for music royalty reporting?","Google BigQuery is the strongest choice for royalty reporting. It handles complex SQL joins across fact and dimension tables, supports dbt natively for transformation pipelines, and its serverless model means you pay only for queries you run. Most music companies doing financial reporting and reconciliation across multiple systems choose BigQuery.",{"question":3161,"answer":3162},"Can ClickHouse replace BigQuery for music analytics?","ClickHouse excels at real-time analytical queries on large event datasets, like streaming play counts or listener behavior. However, it has more limited join capabilities and a smaller BI tool ecosystem. For financial reporting with complex multi-table joins, BigQuery is a better fit. Some companies use both: ClickHouse for real-time dashboards and BigQuery for financial reporting.",{"question":3164,"answer":3165},"How much does it cost to run a music data warehouse?","For a typical mid-size label with 50 million rows: BigQuery costs roughly $120-200/month (storage + queries). Redshift starts around $180-550/month for a minimal cluster. ClickHouse Cloud runs approximately $100-250/month depending on query volume. The biggest cost variable is query frequency, not storage.",{"question":3167,"answer":3168},"What about Elasticsearch or DynamoDB for music data?","Elasticsearch is a search engine, not a data warehouse. It is excellent for full-text catalog search and log analysis but cannot handle financial reporting or complex aggregations. DynamoDB is a transactional key-value store designed for low-latency lookups like user sessions or playlist storage. Neither is suitable as your primary analytics database.",{"src":3170},"/images/blog/musictechlab_blog_database-for-music-data.webp",{"enabled":738,"items":3172},[3173,3175,3177,3179],{"text":3174,"icon":2895},"BigQuery is the best fit for financial reporting, royalty reconciliation, and teams without dedicated DevOps.",{"text":3176,"icon":2939},"ClickHouse delivers sub-second queries on billions of rows, ideal for real-time streaming dashboards.",{"text":3178,"icon":2917},"Redshift fits organizations already invested in the AWS ecosystem with existing data engineering teams.",{"text":3180,"icon":3181},"The right choice depends on your workload pattern, team size, and where your data already lives.","i-lucide-target",{},{"title":3184,"description":3185},"BigQuery vs Redshift vs ClickHouse for Music | MusicTech Lab","Practical comparison of three databases for music royalty data, streaming analytics, and financial reporting. Real pricing and performance numbers.",[3187,3188,3189,731,3190,3191,3192],"bigquery","redshift","clickhouse","data-warehouse","analytics","royalties","9App3vpStCDB3fyT7KUwWU4K2oZ6GdeJD5s-gyCruWA",{"id":3195,"title":686,"authors":3196,"badge":723,"body":3199,"category":756,"client":723,"date":3203,"description":3204,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":3205,"keyTakeaways":723,"meta":3207,"navigation":738,"path":687,"seo":3208,"status":740,"stem":688,"tags":3209,"teaser":3211,"__hash__":3212},"posts/blog/software-development/why-daws-wrong-tool-for-starting-song.md",[3197],{"name":719,"to":720,"avatar":3198},{"src":722},{"type":725,"value":3200,"toc":3201},[],{"title":728,"searchDepth":729,"depth":729,"links":3202},[],"2026-04-22T00:00:00.000Z","DAWs are great for production but kill the flow at the ideation stage. Creativity in music is cyclical and chaotic — current tools force a linear workflow that doesn't match.",{"src":3206},"/images/blog/musictechlab_blog_daw-wrong-tool.webp",{},{"title":686,"description":3204},[764,743,3210,788],"daw","We'll argue that the gap between 'capture' and 'production' needs its own category of tools — and why forcing structure too early kills the best ideas.","4AI328U45cb7sh9Lft7JC-I6lSB50ygH-5rRIBXQD2w",{"id":3214,"title":192,"authors":3215,"badge":723,"body":3218,"category":731,"client":723,"date":3222,"description":3223,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":3224,"keyTakeaways":723,"meta":3226,"navigation":738,"path":193,"seo":3227,"status":740,"stem":194,"tags":3228,"teaser":3229,"__hash__":3230},"posts/blog/music-data/voice-memo-graveyard-problem.md",[3216],{"name":719,"to":720,"avatar":3217},{"src":722},{"type":725,"value":3219,"toc":3220},[],{"title":728,"searchDepth":729,"depth":729,"links":3221},[],"2026-04-15T00:00:00.000Z","Most musical ideas end up as unsearchable, scattered recordings that creators never revisit. Why does this happen and what can we do about it?",{"src":3225},"/images/blog/musictechlab_blog_voice-memo-graveyard.webp",{},{"title":192,"description":3223},[731,787,742,743],"We'll explore why musicians lose their best ideas in a sea of untitled voice memos, and how AI-powered retrieval could change creative workflows forever.","f8rzPNO4gBq8Eu82IRLAZy7yFbobfT8fALOerv-wm6g",{"id":3232,"title":180,"authors":3233,"badge":3236,"body":3238,"category":731,"client":723,"date":3821,"description":3822,"extension":734,"faq":3823,"featured":69,"featuredOrder":723,"hidden":69,"image":3836,"keyTakeaways":3838,"meta":3851,"navigation":738,"path":181,"seo":3852,"status":723,"stem":182,"tags":3855,"teaser":723,"__hash__":3862},"posts/blog/music-data/scout-isrc-metadata-enrichment-spotify-musicbrainz.md",[3234],{"name":719,"to":720,"avatar":3235},{"src":722},{"label":5,"color":3237},"#f59e0b",{"type":725,"value":3239,"toc":3807},[3240,3243,3247,3250,3273,3276,3280,3290,3293,3298,3301,3305,3313,3317,3320,3326,3332,3336,3339,3571,3575,3579,3582,3620,3623,3627,3630,3634,3637,3643,3666,3672,3676,3679,3705,3713,3717,3720,3725,3728,3750,3754,3757,3789,3792,3804],[842,3241,3242],{},"If you have ever opened a 40,000-row CSV and found half the ISRC codes missing, inconsistent, or flat-out wrong, you know the pain. Manually looking up each track via Spotify, MusicBrainz, or ISRC lookup tools takes days. We built Scout to do it in minutes.",[863,3244,3246],{"id":3245},"the-problem-every-catalog-manager-knows","The Problem Every Catalog Manager Knows",[842,3248,3249],{},"Track data arrives as CSV or Excel files. They contain track names, artist names, and sometimes ISRCs. The problem is that \"sometimes\" is doing a lot of heavy lifting in that sentence.",[1045,3251,3253,3258,3263,3268],{"className":3252},[1048,1049,1765,1051,1052],[1054,3254],{"description":3255,"icon":3256,"title":3257},"Tracks without ISRC codes can't be matched to rights holders, leading to unclaimed royalties.","i-lucide-file-warning","Missing ISRCs",[1054,3259],{"description":3260,"icon":3261,"title":3262},"The same recording may have different ISRCs across distributors, DSPs, and catalog systems.","i-lucide-shuffle","Inconsistent ISRCs",[1054,3264],{"description":3265,"icon":3266,"title":3267},"Multiple rows resolve to the same ISRC, inflating track counts and skewing analytics.","i-lucide-copy","Duplicate Entries",[1054,3269],{"description":3270,"icon":3271,"title":3272},"Checking each track one-by-one against web tools or APIs takes 2+ days for a large catalog.","i-lucide-clock","Manual Lookup",[842,3274,3275],{},"This is not a niche issue. Anyone doing soundtrack acquisition, catalog evaluation, royalty reconciliation, or building investor decks hits this wall. The data exists across multiple sources, but nobody connects it at scale.",[863,3277,3279],{"id":3278},"a-real-world-example-santanas-smooth","A Real-World Example: Santana's \"Smooth\"",[842,3281,3282,3283,3286,3287,861],{},"To show why this matters, take a concrete case. Santana's \"Smooth\" appears in a royalty report with ISRC ",[895,3284,3285],{},"USAT29900471",". When Scout looks up the same track on Spotify, it comes back as ",[895,3288,3289],{},"USAR19900033",[842,3291,3292],{},"Both are valid ISRCs for the same recording. The first is from the original 1999 Arista release, the second from a later digital distribution. This happens constantly in the music industry because every release edition (original, remaster, compilation, deluxe, single) can get its own ISRC.",[1572,3294,3295],{},[842,3296,3297],{},"ISRC mismatches between sources do not mean the data is wrong. They mean the same recording exists in multiple editions. Scout flags these for review rather than treating them as errors.",[842,3299,3300],{},"Without automated enrichment, you would either miss this mismatch entirely or spend hours tracking it down manually. Multiply that by 40,000 rows and you understand why catalog managers describe this work as \"soul-crushing.\"",[863,3302,3304],{"id":3303},"what-scout-does","What Scout Does",[842,3306,3307,3308,3312],{},"Scout is a feature inside ",[846,3309,3311],{"href":3310},"/services/musicdata-lab","MusicData Lab"," that batch-enriches track metadata from two authoritative sources: the Spotify API and the MusicBrainz API.",[1074,3314,3316],{"id":3315},"upload-and-map","Upload and Map",[842,3318,3319],{},"Drop a CSV or Excel file. Scout auto-detects common column names (track, artist, ISRC, URL) and lets you adjust the mapping before processing.",[842,3321,3322],{},[1027,3323],{"alt":3324,"src":3325},"Scout upload page","/images/blog/musictechlab_blog_scout-upload.webp",[842,3327,3328],{},[1027,3329],{"alt":3330,"src":3331},"Scout column mapping with auto-detected fields and CSV preview","/images/blog/musictechlab_blog_scout-mapping.webp",[1074,3333,3335],{"id":3334},"enrich-every-track","Enrich Every Track",[842,3337,3338],{},"For each row, Scout runs a four-stage lookup pipeline:",[1013,3340,3344],{"className":3341,"code":3342,"language":3343,"meta":728,"style":728},"language-mermaid shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","flowchart TD\n    A[CSV Row] --> B{Has URL?}\n    B -->|Yes| C[Spotify: lookup by URL]\n    B -->|No| D[Spotify: search by track + artist]\n    C --> E{Spotify match?}\n    D --> E\n    E -->|Yes| F[Track, artist, album, ISRC, release date, popularity]\n    E -->|No| G[No Spotify data]\n\n    F --> H{Has ISRC?}\n    G --> H\n    H -->|Yes| I[MusicBrainz: lookup by ISRC]\n    H -->|No| J[MusicBrainz: search by recording + artist]\n    I --> K{MB match?}\n    J --> K\n    K -->|Yes| L[Artist, release, ISRC, label, country]\n    K -->|No| M[No MusicBrainz data]\n\n    L --> N[ISRC Resolution]\n    M --> N\n    N --> O[Pick best ISRC from Spotify / MB / input]\n\n    O --> P[Flagging]\n    P --> Q{ISRCs disagree?}\n    Q -->|Yes| R[isrc_mismatch]\n    Q -->|No| S{Artist names differ?}\n    R --> S\n    S -->|Yes| T[artist_mismatch]\n    S -->|No| U[Confidence Score]\n    T --> U\n\n    U --> V[Spotify +0.50 / MB +0.30 / ISRC +0.20]\n\n    style A fill:#f8fafc,stroke:#94a3b8,color:#000\n    style F fill:#d1fae5,stroke:#10b981,color:#000\n    style L fill:#dbeafe,stroke:#3b82f6,color:#000\n    style O fill:#fef3c7,stroke:#f59e0b,color:#000\n    style R fill:#fee2e2,stroke:#ef4444,color:#000\n    style T fill:#fee2e2,stroke:#ef4444,color:#000\n    style V fill:#d1fae5,stroke:#10b981,color:#000\n","mermaid",[895,3345,3346,3351,3356,3361,3366,3371,3376,3381,3386,3391,3396,3402,3407,3413,3419,3425,3431,3437,3442,3448,3454,3460,3465,3471,3477,3483,3489,3495,3501,3507,3513,3518,3524,3529,3535,3541,3547,3553,3559,3565],{"__ignoreMap":728},[1086,3347,3348],{"class":1088,"line":1089},[1086,3349,3350],{"class":1436},"flowchart TD\n",[1086,3352,3353],{"class":1088,"line":729},[1086,3354,3355],{"class":1436},"    A[CSV Row] --> B{Has URL?}\n",[1086,3357,3358],{"class":1088,"line":1112},[1086,3359,3360],{"class":1436},"    B -->|Yes| C[Spotify: lookup by URL]\n",[1086,3362,3363],{"class":1088,"line":1181},[1086,3364,3365],{"class":1436},"    B -->|No| D[Spotify: search by track + artist]\n",[1086,3367,3368],{"class":1088,"line":1205},[1086,3369,3370],{"class":1436},"    C --> E{Spotify match?}\n",[1086,3372,3373],{"class":1088,"line":1276},[1086,3374,3375],{"class":1436},"    D --> E\n",[1086,3377,3378],{"class":1088,"line":1282},[1086,3379,3380],{"class":1436},"    E -->|Yes| F[Track, artist, album, ISRC, release date, popularity]\n",[1086,3382,3383],{"class":1088,"line":1288},[1086,3384,3385],{"class":1436},"    E -->|No| G[No Spotify data]\n",[1086,3387,3388],{"class":1088,"line":2685},[1086,3389,3390],{"emptyLinePlaceholder":738},"\n",[1086,3392,3393],{"class":1088,"line":2700},[1086,3394,3395],{"class":1436},"    F --> H{Has ISRC?}\n",[1086,3397,3399],{"class":1088,"line":3398},11,[1086,3400,3401],{"class":1436},"    G --> H\n",[1086,3403,3404],{"class":1088,"line":1715},[1086,3405,3406],{"class":1436},"    H -->|Yes| I[MusicBrainz: lookup by ISRC]\n",[1086,3408,3410],{"class":1088,"line":3409},13,[1086,3411,3412],{"class":1436},"    H -->|No| J[MusicBrainz: search by recording + artist]\n",[1086,3414,3416],{"class":1088,"line":3415},14,[1086,3417,3418],{"class":1436},"    I --> K{MB match?}\n",[1086,3420,3422],{"class":1088,"line":3421},15,[1086,3423,3424],{"class":1436},"    J --> K\n",[1086,3426,3428],{"class":1088,"line":3427},16,[1086,3429,3430],{"class":1436},"    K -->|Yes| L[Artist, release, ISRC, label, country]\n",[1086,3432,3434],{"class":1088,"line":3433},17,[1086,3435,3436],{"class":1436},"    K -->|No| M[No MusicBrainz data]\n",[1086,3438,3440],{"class":1088,"line":3439},18,[1086,3441,3390],{"emptyLinePlaceholder":738},[1086,3443,3445],{"class":1088,"line":3444},19,[1086,3446,3447],{"class":1436},"    L --> N[ISRC Resolution]\n",[1086,3449,3451],{"class":1088,"line":3450},20,[1086,3452,3453],{"class":1436},"    M --> N\n",[1086,3455,3457],{"class":1088,"line":3456},21,[1086,3458,3459],{"class":1436},"    N --> O[Pick best ISRC from Spotify / MB / input]\n",[1086,3461,3463],{"class":1088,"line":3462},22,[1086,3464,3390],{"emptyLinePlaceholder":738},[1086,3466,3468],{"class":1088,"line":3467},23,[1086,3469,3470],{"class":1436},"    O --> P[Flagging]\n",[1086,3472,3474],{"class":1088,"line":3473},24,[1086,3475,3476],{"class":1436},"    P --> Q{ISRCs disagree?}\n",[1086,3478,3480],{"class":1088,"line":3479},25,[1086,3481,3482],{"class":1436},"    Q -->|Yes| R[isrc_mismatch]\n",[1086,3484,3486],{"class":1088,"line":3485},26,[1086,3487,3488],{"class":1436},"    Q -->|No| S{Artist names differ?}\n",[1086,3490,3492],{"class":1088,"line":3491},27,[1086,3493,3494],{"class":1436},"    R --> S\n",[1086,3496,3498],{"class":1088,"line":3497},28,[1086,3499,3500],{"class":1436},"    S -->|Yes| T[artist_mismatch]\n",[1086,3502,3504],{"class":1088,"line":3503},29,[1086,3505,3506],{"class":1436},"    S -->|No| U[Confidence Score]\n",[1086,3508,3510],{"class":1088,"line":3509},30,[1086,3511,3512],{"class":1436},"    T --> U\n",[1086,3514,3516],{"class":1088,"line":3515},31,[1086,3517,3390],{"emptyLinePlaceholder":738},[1086,3519,3521],{"class":1088,"line":3520},32,[1086,3522,3523],{"class":1436},"    U --> V[Spotify +0.50 / MB +0.30 / ISRC +0.20]\n",[1086,3525,3527],{"class":1088,"line":3526},33,[1086,3528,3390],{"emptyLinePlaceholder":738},[1086,3530,3532],{"class":1088,"line":3531},34,[1086,3533,3534],{"class":1436},"    style A fill:#f8fafc,stroke:#94a3b8,color:#000\n",[1086,3536,3538],{"class":1088,"line":3537},35,[1086,3539,3540],{"class":1436},"    style F fill:#d1fae5,stroke:#10b981,color:#000\n",[1086,3542,3544],{"class":1088,"line":3543},36,[1086,3545,3546],{"class":1436},"    style L fill:#dbeafe,stroke:#3b82f6,color:#000\n",[1086,3548,3550],{"class":1088,"line":3549},37,[1086,3551,3552],{"class":1436},"    style O fill:#fef3c7,stroke:#f59e0b,color:#000\n",[1086,3554,3556],{"class":1088,"line":3555},38,[1086,3557,3558],{"class":1436},"    style R fill:#fee2e2,stroke:#ef4444,color:#000\n",[1086,3560,3562],{"class":1088,"line":3561},39,[1086,3563,3564],{"class":1436},"    style T fill:#fee2e2,stroke:#ef4444,color:#000\n",[1086,3566,3568],{"class":1088,"line":3567},40,[1086,3569,3570],{"class":1436},"    style V fill:#d1fae5,stroke:#10b981,color:#000\n",[3572,3573],"project-timeline",{":items":3574},"[{\"title\":\"Spotify Lookup\",\"description\":\"Searches by track URL (if present), then by track name + artist name. Returns track name, artist, album, ISRC, release date, popularity, and a direct Spotify link.\",\"icon\":\"i-lucide-disc-3\"},{\"title\":\"MusicBrainz Lookup\",\"description\":\"Searches by ISRC first (using the Spotify ISRC or input ISRC), then falls back to recording name + artist. Returns artist, release, ISRC, label, and country.\",\"icon\":\"i-lucide-database\"},{\"title\":\"ISRC Resolution\",\"description\":\"Picks the best ISRC from all available sources (Spotify, MusicBrainz, input) and assigns it as the resolved identifier for the track.\",\"icon\":\"i-lucide-git-merge\"},{\"title\":\"Flagging\",\"description\":\"Detects ISRC mismatches between sources, artist name mismatches (using fuzzy matching), and duplicate ISRCs across rows.\",\"icon\":\"i-lucide-flag\"}]",[1074,3576,3578],{"id":3577},"confidence-scoring","Confidence Scoring",[842,3580,3581],{},"Each track gets a confidence score from 0.00 to 1.00:",[871,3583,3584,3594],{},[874,3585,3586],{},[877,3587,3588,3591],{},[880,3589,3590],{},"Source",[880,3592,3593],{},"Score",[887,3595,3596,3604,3612],{},[877,3597,3598,3601],{},[892,3599,3600],{},"Spotify match",[892,3602,3603],{},"+0.50",[877,3605,3606,3609],{},[892,3607,3608],{},"MusicBrainz match",[892,3610,3611],{},"+0.30",[877,3613,3614,3617],{},[892,3615,3616],{},"Resolved ISRC",[892,3618,3619],{},"+0.20",[842,3621,3622],{},"A track matched by both Spotify and MusicBrainz with a resolved ISRC scores a perfect 1.00. Flags are informational only. They tell you something needs attention without penalizing the match quality.",[1074,3624,3626],{"id":3625},"export","Export",[842,3628,3629],{},"When processing completes, download a clean CSV with all original data plus every enriched field: Spotify metadata, MusicBrainz metadata, resolved ISRCs, status, flags, and confidence scores. Ready for analysis, investor decks, or import into your rights management system.",[863,3631,3633],{"id":3632},"processing-at-scale","Processing at Scale",[842,3635,3636],{},"Scout processes files asynchronously using Celery workers. A 40,000-row file runs in the background while you continue working. The job detail page shows real-time progress without page refreshes:",[842,3638,3639],{},[1027,3640],{"alt":3641,"src":3642},"Live streaming logs during enrichment","/images/blog/musictechlab_blog_scout-logs.webp",[1045,3644,3646,3651,3656,3661],{"className":3645},[1048,1049,1765,1051,1052],[1054,3647],{"description":3648,"icon":3649,"title":3650},"Progress bar, track counts, and status badges update via AJAX polling every 3 seconds.","i-lucide-activity","Live Progress",[1054,3652],{"description":3653,"icon":3654,"title":3655},"Watch Spotify and MusicBrainz lookups happen in real time in the Logs tab.","i-lucide-scroll-text","Streaming Logs",[1054,3657],{"description":3658,"icon":3659,"title":3660},"Search and filter results by track, artist, ISRC, status, confidence range, or flags.","i-lucide-filter","Column Filters",[1054,3662],{"description":3663,"icon":3664,"title":3665},"Download enriched data as a clean, grouped CSV when processing completes.","i-lucide-file-down","CSV Export",[842,3667,3668],{},[1027,3669],{"alt":3670,"src":3671},"Completed job with enriched tracks, confidence scores, and flags","/images/blog/musictechlab_blog_scout-results.webp",[863,3673,3675],{"id":3674},"the-flags-that-matter","The Flags That Matter",[842,3677,3678],{},"Scout raises four types of flags:",[958,3680,3681,3687,3693,3699],{},[961,3682,3683,3686],{},[996,3684,3685],{},"isrc_mismatch"," - the input ISRC, Spotify ISRC, and MusicBrainz ISRC do not all agree. Most common flag. Usually means different release editions.",[961,3688,3689,3692],{},[996,3690,3691],{},"artist_mismatch"," - the artist name from Spotify and MusicBrainz has a fuzzy match score below 75%. Can indicate featuring artists, name variations, or genuine data issues.",[961,3694,3695,3698],{},[996,3696,3697],{},"duplicate_isrc"," - multiple rows in your file resolve to the same ISRC. Important for catching double-counted tracks.",[961,3700,3701,3704],{},[996,3702,3703],{},"enrichment_error"," - the lookup failed for technical reasons (API timeout, rate limit, etc.).",[1032,3706,3707],{},[842,3708,3709,3710,3712],{},"Filter by ",[895,3711,3685],{}," to quickly review all tracks where your input ISRC differs from what Spotify and MusicBrainz report. These are your highest-priority reconciliation items.",[863,3714,3716],{"id":3715},"from-script-to-product","From Script to Product",[842,3718,3719],{},"The inspiration for Scout came from a real conversation with a music professional doing soundtrack acquisition work. They had built a Python script that batch-processed metadata from Spotify and MusicBrainz APIs using pandas. No AI needed, just API calls and data wrangling.",[1032,3721,3722],{},[842,3723,3724],{},"It cut a 2-day manual task down to 30 minutes.",[842,3726,3727],{},"We took that exact workflow and productized it inside MusicData Lab:",[1045,3729,3731,3736,3741,3746],{"className":3730},[1048,1049,1765,1051,1052],[1054,3732],{"description":3733,"icon":3734,"title":3735},"Upload a file, map columns, click run.","i-lucide-mouse-pointer-click","No Coding Required",[1054,3737],{"description":3738,"icon":3739,"title":3740},"Works with any CSV or Excel file, auto-detects common column layouts.","i-lucide-file-spreadsheet","Any File Format",[1054,3742],{"description":3743,"icon":3744,"title":3745},"Jobs, tracks, and logs are stored in the database. Come back to any job weeks later.","i-lucide-hard-drive","Persistent Results",[1054,3747],{"description":3748,"icon":1769,"title":3749},"Background processing with live progress, not a script that blocks your terminal.","Built for Large Catalogs",[863,3751,3753],{"id":3752},"who-this-is-for","Who This Is For",[842,3755,3756],{},"Scout is built for anyone who deals with music metadata at scale:",[958,3758,3759,3765,3771,3777,3783],{},[961,3760,3761,3764],{},[996,3762,3763],{},"Rights managers"," reconciling royalty statements across distributors",[961,3766,3767,3770],{},[996,3768,3769],{},"A&R teams"," evaluating catalogs for acquisition",[961,3772,3773,3776],{},[996,3774,3775],{},"Independent labels"," cleaning up their metadata before pitching to sync agents",[961,3778,3779,3782],{},[996,3780,3781],{},"Royalty analysts"," spotting unclaimed revenue from ISRC gaps",[961,3784,3785,3788],{},[996,3786,3787],{},"Catalog managers"," preparing clean data for investor decks or audits",[842,3790,3791],{},"If you are spending days in spreadsheets manually looking up ISRCs, Scout does that work for you and flags the problems worth your attention.",[1572,3793,3794],{},[842,3795,3796,3797,3799,3800,861],{},"Scout is part of ",[846,3798,3311],{"href":3310},", our music distribution analytics platform. If you are dealing with messy catalog data and want to see Scout in action, ",[846,3801,3803],{"href":3802},"/contact","get in touch",[1680,3805,3806],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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);}",{"title":728,"searchDepth":729,"depth":729,"links":3808},[3809,3810,3811,3817,3818,3819,3820],{"id":3245,"depth":729,"text":3246},{"id":3278,"depth":729,"text":3279},{"id":3303,"depth":729,"text":3304,"children":3812},[3813,3814,3815,3816],{"id":3315,"depth":1112,"text":3316},{"id":3334,"depth":1112,"text":3335},{"id":3577,"depth":1112,"text":3578},{"id":3625,"depth":1112,"text":3626},{"id":3632,"depth":729,"text":3633},{"id":3674,"depth":729,"text":3675},{"id":3715,"depth":729,"text":3716},{"id":3752,"depth":729,"text":3753},"2026-04-04T00:00:00.000Z","How we built Scout to batch-enrich music metadata via Spotify and MusicBrainz APIs, flag ISRC mismatches, and export clean CSVs for catalog evaluation and royalty reconciliation.",[3824,3827,3830,3833],{"question":3825,"answer":3826},"What is Scout in MusicData Lab?","Scout is a batch metadata enrichment tool that takes CSV or Excel files with track data and enriches every row with ISRCs, artist names, album info, and release dates from Spotify and MusicBrainz APIs.",{"question":3828,"answer":3829},"Why do the same tracks have different ISRCs?","Multiple ISRCs can exist for the same recording because each release edition (original, remaster, compilation, deluxe) gets its own ISRC. This is the most common source of metadata inconsistency in royalty reports.",{"question":3831,"answer":3832},"What file formats does Scout support?","Scout works with any CSV or Excel file. It auto-detects column mappings for common distributor and DSP report formats.",{"question":3834,"answer":3835},"How does the confidence score work?","Scout scores each track on a 0.00 to 1.00 scale: Spotify match adds +0.50, MusicBrainz match adds +0.30, and a resolved ISRC adds +0.20. Flags like ISRC mismatch or artist mismatch are informational and do not reduce the score.",{"src":3837},"/images/blog/musictechlab_blog_scout-isrc-metadata-enrichment.webp",{"enabled":738,"items":3839},[3840,3842,3845,3848],{"text":3841,"icon":2939},"Scout batch-enriches 40,000+ tracks from Spotify and MusicBrainz APIs in minutes, not days.",{"text":3843,"icon":3844},"Confidence scores range from 0.00 to 1.00: Spotify +0.50, MusicBrainz +0.30, resolved ISRC +0.20.","i-lucide-bar-chart-3",{"text":3846,"icon":3847},"ISRC mismatches between sources usually mean different release editions, not data errors.","i-lucide-alert-triangle",{"text":3849,"icon":3850},"Exports a clean CSV with all enriched fields ready for rights management or investor decks.","i-lucide-file-text",{},{"title":3853,"description":3854},"Scout: Batch ISRC Metadata Enrichment via Spotify & MusicBrainz | MusicTech Lab","Enrich 40k+ tracks with ISRCs, artist data, and album info from Spotify and MusicBrainz. Flag mismatches, resolve duplicates, export clean CSVs.",[3856,3857,3858,3859,3860,3861,764],"ISRC","metadata","Spotify API","MusicBrainz","catalog management","royalty reconciliation","QUzxxiFztMoXqat8s7uV0SkFilEWyxNGrkrFsKLQLas",{"id":3864,"title":694,"authors":3865,"badge":3868,"body":3869,"category":756,"client":723,"date":4966,"description":4967,"extension":734,"faq":4968,"featured":69,"featuredOrder":723,"hidden":69,"image":4978,"keyTakeaways":723,"meta":4980,"navigation":738,"path":695,"seo":4981,"status":723,"stem":696,"tags":4984,"teaser":723,"__hash__":4991},"posts/blog/software-development/why-we-dont-build-chat-from-scratch.md",[3866],{"name":719,"to":720,"avatar":3867},{"src":722},{"label":5,"color":3237},{"type":725,"value":3870,"toc":4953},[3871,3874,3878,3881,3884,3888,3891,3921,3924,3929,3933,3942,3945,3968,3971,3975,3983,3987,4007,4011,4018,4023,4026,4277,4583,4592,4597,4600,4804,4814,4820,4824,4857,4861,4864,4869,4883,4888,4899,4902,4906,4926,4930,4933,4936,4939,4950],[842,3872,3873],{},"\"We need a chat feature.\" Five words that sound simple but hide a massive engineering project underneath. We hear this request regularly from clients who are building platforms where users need to communicate. And every time, our answer is the same: we don't build chat from scratch. Here's why.",[863,3875,3877],{"id":3876},"the-request-that-sounds-simple","The request that sounds simple",[842,3879,3880],{},"A client comes to us with a platform idea. Users need to message each other. Maybe it's artists talking to managers, editors talking to sound designers, or customers talking to support. The feature request fits in one sentence: \"Add a chat.\"",[842,3882,3883],{},"From the outside, it looks straightforward. A text input, a send button, messages appearing on screen. How hard can it be?",[863,3885,3887],{"id":3886},"what-building-chat-actually-means","What building chat actually means",[842,3889,3890],{},"Very hard. Real-time messaging is one of those features that looks like 10% of the work but turns into 60% if you try to build it yourself. Here's what's hiding behind that simple chat bubble:",[1045,3892,3894,3899,3903,3907,3912,3917],{"className":3893},[1048,1049,1765,1051,1052],[1054,3895],{"description":3896,"icon":3897,"title":3898},"Persistent connections, reconnection logic, heartbeats, connection state management across devices.","i-lucide-wifi","WebSocket Infrastructure",[1054,3900],{"description":3901,"icon":1057,"title":3902},"Message ordering, deduplication, retry logic. What happens when the network drops mid-message?","Delivery Guarantees",[1054,3904],{"description":3905,"icon":2895,"title":3906},"History, search, pagination, attachments. Every message needs to be stored, indexed, and retrievable.","Message Storage",[1054,3908],{"description":3909,"icon":3910,"title":3911},"Mobile push, desktop notifications, badge counts. Different platforms, different APIs.","i-lucide-bell","Push Notifications",[1054,3913],{"description":3914,"icon":3915,"title":3916},"Who's online? Who read the message? When? This needs real-time sync across all connected clients.","i-lucide-eye","Read Receipts & Presence",[1054,3918],{"description":3919,"icon":3920,"title":2040},"What works for 10 users breaks at 1,000. What works at 1,000 breaks at 100,000.","i-lucide-trending-up",[842,3922,3923],{},"Companies like Slack, WhatsApp, and Discord have entire engineering teams dedicated to messaging infrastructure. That's their core product. When chat is just one feature in your app, spending months building and maintaining this infrastructure doesn't make sense.",[1901,3925,3926],{},[842,3927,3928],{},"Building real-time chat from scratch typically takes 3-6 months of dedicated engineering time. That's time and budget taken away from the features that make your product unique.",[863,3930,3932],{"id":3931},"the-smart-approach-managed-chat-services","The smart approach: managed chat services",[842,3934,3935,3936,3941],{},"Services like ",[846,3937,3940],{"href":3938,"rel":3939},"https://getstream.io",[850],"Stream",", Sendbird, and PubNub exist specifically to solve this problem. They provide battle-tested messaging infrastructure through APIs and SDKs. You get years of engineering in a single integration.",[842,3943,3944],{},"What a managed service gives you out of the box:",[958,3946,3947,3950,3953,3956,3959,3962,3965],{},[961,3948,3949],{},"Real-time message delivery with offline support",[961,3951,3952],{},"Message history, search, and threading",[961,3954,3955],{},"Read receipts and typing indicators",[961,3957,3958],{},"Unread counts and push notifications",[961,3960,3961],{},"File and image attachments",[961,3963,3964],{},"Moderation and content filtering",[961,3966,3967],{},"SDKs for web, iOS, Android, Flutter, React Native",[842,3969,3970],{},"The cost? A predictable monthly fee based on usage, instead of months of custom development and ongoing maintenance.",[863,3972,3974],{"id":3973},"how-we-did-it-the-artist-suite","How we did it: The Artist Suite",[842,3976,3977,3978,3982],{},"We recently built ",[846,3979,3981],{"href":3980},"/case-studies","The Artist Suite",", a platform connecting music artists with industry professionals. One of the core requirements was direct messaging between users. Artists needed to talk to managers, producers, and collaborators directly inside the app.",[1074,3984,3986],{"id":3985},"the-stack","The stack",[958,3988,3989,3995,4001],{},[961,3990,3991,3994],{},[996,3992,3993],{},"Backend:"," Django with Django REST Framework",[961,3996,3997,4000],{},[996,3998,3999],{},"Frontend:"," Nuxt 4 (Vue 3) with SSR",[961,4002,4003,4006],{},[996,4004,4005],{},"Chat provider:"," Stream Chat",[1074,4008,4010],{"id":4009},"the-integration","The integration",[842,4012,4013,4014,4017],{},"The entire chat feature was shipped in ",[996,4015,4016],{},"one sprint"," (two weeks). Here's how it works:",[842,4019,4020],{},[996,4021,4022],{},"Backend (3 endpoints, ~100 lines of code):",[842,4024,4025],{},"The Django backend handles authentication and channel management. Stream never sees your users' passwords. The backend generates short-lived tokens and manages channel creation.",[1013,4027,4030],{"className":1368,"code":4028,"filename":4029,"language":1250,"meta":728,"style":728},"class TokenView(APIView):\n    \"\"\"Generate a Stream Chat token for the authenticated user.\"\"\"\n\n    def post(self, request):\n        client = StreamChat(\n            api_key=settings.STREAM_API_KEY,\n            api_secret=settings.STREAM_API_SECRET,\n        )\n        # Upsert user info to Stream\n        client.upsert_user({\"id\": str(request.user.id), \"name\": request.user.name})\n        token = client.create_token(str(request.user.id))\n        return Response({\"token\": token, \"api_key\": settings.STREAM_API_KEY})\n","views.py",[895,4031,4032,4048,4057,4061,4082,4095,4113,4129,4134,4139,4201,4235],{"__ignoreMap":728},[1086,4033,4034,4037,4040,4042,4045],{"class":1088,"line":1089},[1086,4035,4036],{"class":1155},"class",[1086,4038,4039],{"class":1092}," TokenView",[1086,4041,1398],{"class":1146},[1086,4043,4044],{"class":1092},"APIView",[1086,4046,4047],{"class":1146},"):\n",[1086,4049,4050,4052,4055],{"class":1088,"line":729},[1086,4051,1424],{"class":1423},[1086,4053,4054],{"class":1427},"Generate a Stream Chat token for the authenticated user.",[1086,4056,1431],{"class":1423},[1086,4058,4059],{"class":1088,"line":1112},[1086,4060,3390],{"emptyLinePlaceholder":738},[1086,4062,4063,4066,4069,4071,4075,4077,4080],{"class":1088,"line":1181},[1086,4064,4065],{"class":1155},"    def",[1086,4067,4068],{"class":1105}," post",[1086,4070,1398],{"class":1146},[1086,4072,4074],{"class":4073},"s5tWE","self",[1086,4076,1227],{"class":1146},[1086,4078,4079],{"class":1401}," request",[1086,4081,4047],{"class":1146},[1086,4083,4084,4087,4089,4092],{"class":1088,"line":1205},[1086,4085,4086],{"class":1436},"        client ",[1086,4088,1440],{"class":1146},[1086,4090,4091],{"class":1105}," StreamChat",[1086,4093,4094],{"class":1146},"(\n",[1086,4096,4097,4100,4102,4105,4107,4111],{"class":1088,"line":1276},[1086,4098,4099],{"class":1401},"            api_key",[1086,4101,1440],{"class":1146},[1086,4103,4104],{"class":1105},"settings",[1086,4106,861],{"class":1146},[1086,4108,4110],{"class":4109},"swJcz","STREAM_API_KEY",[1086,4112,1202],{"class":1146},[1086,4114,4115,4118,4120,4122,4124,4127],{"class":1088,"line":1282},[1086,4116,4117],{"class":1401},"            api_secret",[1086,4119,1440],{"class":1146},[1086,4121,4104],{"class":1105},[1086,4123,861],{"class":1146},[1086,4125,4126],{"class":4109},"STREAM_API_SECRET",[1086,4128,1202],{"class":1146},[1086,4130,4131],{"class":1088,"line":1288},[1086,4132,4133],{"class":1146},"        )\n",[1086,4135,4136],{"class":1088,"line":2685},[1086,4137,4138],{"class":1427},"        # Upsert user info to Stream\n",[1086,4140,4141,4144,4146,4149,4152,4154,4157,4159,4161,4163,4165,4168,4170,4173,4175,4177,4180,4182,4185,4187,4189,4191,4193,4195,4197,4199],{"class":1088,"line":2700},[1086,4142,4143],{"class":1436},"        client",[1086,4145,861],{"class":1146},[1086,4147,4148],{"class":1105},"upsert_user",[1086,4150,4151],{"class":1146},"({",[1086,4153,1159],{"class":1146},[1086,4155,4156],{"class":1096},"id",[1086,4158,1159],{"class":1146},[1086,4160,1133],{"class":1146},[1086,4162,1407],{"class":1092},[1086,4164,1398],{"class":1146},[1086,4166,4167],{"class":1105},"request",[1086,4169,861],{"class":1146},[1086,4171,4172],{"class":4109},"user",[1086,4174,861],{"class":1146},[1086,4176,4156],{"class":4109},[1086,4178,4179],{"class":1146},"),",[1086,4181,1195],{"class":1146},[1086,4183,4184],{"class":1096},"name",[1086,4186,1159],{"class":1146},[1086,4188,1133],{"class":1146},[1086,4190,4079],{"class":1105},[1086,4192,861],{"class":1146},[1086,4194,4172],{"class":4109},[1086,4196,861],{"class":1146},[1086,4198,4184],{"class":4109},[1086,4200,1567],{"class":1146},[1086,4202,4203,4206,4208,4210,4212,4215,4217,4220,4222,4224,4226,4228,4230,4232],{"class":1088,"line":3398},[1086,4204,4205],{"class":1436},"        token ",[1086,4207,1440],{"class":1146},[1086,4209,1443],{"class":1436},[1086,4211,861],{"class":1146},[1086,4213,4214],{"class":1105},"create_token",[1086,4216,1398],{"class":1146},[1086,4218,4219],{"class":1092},"str",[1086,4221,1398],{"class":1146},[1086,4223,4167],{"class":1105},[1086,4225,861],{"class":1146},[1086,4227,4172],{"class":4109},[1086,4229,861],{"class":1146},[1086,4231,4156],{"class":4109},[1086,4233,4234],{"class":1146},"))\n",[1086,4236,4237,4240,4243,4245,4247,4250,4252,4254,4257,4259,4261,4264,4266,4268,4271,4273,4275],{"class":1088,"line":1715},[1086,4238,4239],{"class":1423},"        return",[1086,4241,4242],{"class":1105}," Response",[1086,4244,4151],{"class":1146},[1086,4246,1159],{"class":1146},[1086,4248,4249],{"class":1096},"token",[1086,4251,1159],{"class":1146},[1086,4253,1133],{"class":1146},[1086,4255,4256],{"class":1105}," token",[1086,4258,1227],{"class":1146},[1086,4260,1195],{"class":1146},[1086,4262,4263],{"class":1096},"api_key",[1086,4265,1159],{"class":1146},[1086,4267,1133],{"class":1146},[1086,4269,4270],{"class":1105}," settings",[1086,4272,861],{"class":1146},[1086,4274,4110],{"class":4109},[1086,4276,1567],{"class":1146},[1013,4278,4280],{"className":1368,"code":4279,"filename":4029,"language":1250,"meta":728,"style":728},"class CreateChannelView(APIView):\n    \"\"\"Create or get a messaging channel between two users.\"\"\"\n\n    def post(self, request):\n        other_user_id = request.data[\"user_id\"]\n        # Deterministic channel ID from sorted user IDs\n        members = sorted([str(request.user.id), str(other_user_id)])\n        channel_id = f\"chat-{members[0]}-{members[1]}\"\n\n        client = StreamChat(\n            api_key=settings.STREAM_API_KEY,\n            api_secret=settings.STREAM_API_SECRET,\n        )\n        channel = client.channel(\"messaging\", channel_id, {\"members\": members})\n        channel.create(str(request.user.id))\n        return Response({\"channel_id\": channel_id})\n",[895,4281,4282,4295,4304,4308,4324,4350,4355,4394,4442,4446,4456,4470,4484,4488,4534,4562],{"__ignoreMap":728},[1086,4283,4284,4286,4289,4291,4293],{"class":1088,"line":1089},[1086,4285,4036],{"class":1155},[1086,4287,4288],{"class":1092}," CreateChannelView",[1086,4290,1398],{"class":1146},[1086,4292,4044],{"class":1092},[1086,4294,4047],{"class":1146},[1086,4296,4297,4299,4302],{"class":1088,"line":729},[1086,4298,1424],{"class":1423},[1086,4300,4301],{"class":1427},"Create or get a messaging channel between two users.",[1086,4303,1431],{"class":1423},[1086,4305,4306],{"class":1088,"line":1112},[1086,4307,3390],{"emptyLinePlaceholder":738},[1086,4309,4310,4312,4314,4316,4318,4320,4322],{"class":1088,"line":1181},[1086,4311,4065],{"class":1155},[1086,4313,4068],{"class":1105},[1086,4315,1398],{"class":1146},[1086,4317,4074],{"class":4073},[1086,4319,1227],{"class":1146},[1086,4321,4079],{"class":1401},[1086,4323,4047],{"class":1146},[1086,4325,4326,4329,4331,4333,4335,4338,4341,4343,4346,4348],{"class":1088,"line":1205},[1086,4327,4328],{"class":1436},"        other_user_id ",[1086,4330,1440],{"class":1146},[1086,4332,4079],{"class":1436},[1086,4334,861],{"class":1146},[1086,4336,4337],{"class":4109},"data",[1086,4339,4340],{"class":1146},"[",[1086,4342,1159],{"class":1146},[1086,4344,4345],{"class":1096},"user_id",[1086,4347,1159],{"class":1146},[1086,4349,1273],{"class":1146},[1086,4351,4352],{"class":1088,"line":1276},[1086,4353,4354],{"class":1427},"        # Deterministic channel ID from sorted user IDs\n",[1086,4356,4357,4360,4362,4365,4368,4370,4372,4374,4376,4378,4380,4382,4384,4386,4388,4391],{"class":1088,"line":1282},[1086,4358,4359],{"class":1436},"        members ",[1086,4361,1440],{"class":1146},[1086,4363,4364],{"class":1105}," sorted",[1086,4366,4367],{"class":1146},"([",[1086,4369,4219],{"class":1092},[1086,4371,1398],{"class":1146},[1086,4373,4167],{"class":1105},[1086,4375,861],{"class":1146},[1086,4377,4172],{"class":4109},[1086,4379,861],{"class":1146},[1086,4381,4156],{"class":4109},[1086,4383,4179],{"class":1146},[1086,4385,1407],{"class":1092},[1086,4387,1398],{"class":1146},[1086,4389,4390],{"class":1105},"other_user_id",[1086,4392,4393],{"class":1146},")])\n",[1086,4395,4396,4399,4401,4404,4407,4410,4413,4415,4418,4421,4424,4426,4428,4430,4432,4435,4437,4439],{"class":1088,"line":1288},[1086,4397,4398],{"class":1436},"        channel_id ",[1086,4400,1440],{"class":1146},[1086,4402,4403],{"class":1155}," f",[1086,4405,4406],{"class":1096},"\"chat-",[1086,4408,4409],{"class":1187},"{",[1086,4411,4412],{"class":1436},"members",[1086,4414,4340],{"class":1146},[1086,4416,4417],{"class":1187},"0",[1086,4419,4420],{"class":1146},"]",[1086,4422,4423],{"class":1187},"}",[1086,4425,2635],{"class":1096},[1086,4427,4409],{"class":1187},[1086,4429,4412],{"class":1436},[1086,4431,4340],{"class":1146},[1086,4433,4434],{"class":1187},"1",[1086,4436,4420],{"class":1146},[1086,4438,4423],{"class":1187},[1086,4440,4441],{"class":1096},"\"\n",[1086,4443,4444],{"class":1088,"line":2685},[1086,4445,3390],{"emptyLinePlaceholder":738},[1086,4447,4448,4450,4452,4454],{"class":1088,"line":2700},[1086,4449,4086],{"class":1436},[1086,4451,1440],{"class":1146},[1086,4453,4091],{"class":1105},[1086,4455,4094],{"class":1146},[1086,4457,4458,4460,4462,4464,4466,4468],{"class":1088,"line":3398},[1086,4459,4099],{"class":1401},[1086,4461,1440],{"class":1146},[1086,4463,4104],{"class":1105},[1086,4465,861],{"class":1146},[1086,4467,4110],{"class":4109},[1086,4469,1202],{"class":1146},[1086,4471,4472,4474,4476,4478,4480,4482],{"class":1088,"line":1715},[1086,4473,4117],{"class":1401},[1086,4475,1440],{"class":1146},[1086,4477,4104],{"class":1105},[1086,4479,861],{"class":1146},[1086,4481,4126],{"class":4109},[1086,4483,1202],{"class":1146},[1086,4485,4486],{"class":1088,"line":3409},[1086,4487,4133],{"class":1146},[1086,4489,4490,4493,4495,4497,4499,4502,4504,4506,4509,4511,4513,4516,4518,4521,4523,4525,4527,4529,4532],{"class":1088,"line":3415},[1086,4491,4492],{"class":1436},"        channel ",[1086,4494,1440],{"class":1146},[1086,4496,1443],{"class":1436},[1086,4498,861],{"class":1146},[1086,4500,4501],{"class":1105},"channel",[1086,4503,1398],{"class":1146},[1086,4505,1159],{"class":1146},[1086,4507,4508],{"class":1096},"messaging",[1086,4510,1159],{"class":1146},[1086,4512,1227],{"class":1146},[1086,4514,4515],{"class":1105}," channel_id",[1086,4517,1227],{"class":1146},[1086,4519,4520],{"class":1146}," {",[1086,4522,1159],{"class":1146},[1086,4524,4412],{"class":1096},[1086,4526,1159],{"class":1146},[1086,4528,1133],{"class":1146},[1086,4530,4531],{"class":1105}," members",[1086,4533,1567],{"class":1146},[1086,4535,4536,4539,4541,4544,4546,4548,4550,4552,4554,4556,4558,4560],{"class":1088,"line":3421},[1086,4537,4538],{"class":1436},"        channel",[1086,4540,861],{"class":1146},[1086,4542,4543],{"class":1105},"create",[1086,4545,1398],{"class":1146},[1086,4547,4219],{"class":1092},[1086,4549,1398],{"class":1146},[1086,4551,4167],{"class":1105},[1086,4553,861],{"class":1146},[1086,4555,4172],{"class":4109},[1086,4557,861],{"class":1146},[1086,4559,4156],{"class":4109},[1086,4561,4234],{"class":1146},[1086,4563,4564,4566,4568,4570,4572,4575,4577,4579,4581],{"class":1088,"line":3427},[1086,4565,4239],{"class":1423},[1086,4567,4242],{"class":1105},[1086,4569,4151],{"class":1146},[1086,4571,1159],{"class":1146},[1086,4573,4574],{"class":1096},"channel_id",[1086,4576,1159],{"class":1146},[1086,4578,1133],{"class":1146},[1086,4580,4515],{"class":1105},[1086,4582,1567],{"class":1146},[1032,4584,4585],{},[842,4586,4587,4588,4591],{},"The deterministic channel ID pattern (",[895,4589,4590],{},"chat-{user1}-{user2}"," with sorted IDs) means you always get the same channel for the same pair of users, no matter who initiates the conversation.",[842,4593,4594],{},[996,4595,4596],{},"Frontend (Vue composable + component):",[842,4598,4599],{},"The frontend connects to Stream using the token from the backend. All real-time updates, message rendering, and state management happen through Stream's JavaScript SDK.",[1013,4601,4606],{"className":4602,"code":4603,"filename":4604,"language":4605,"meta":728,"style":728},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export async function inboxGetToken(): Promise\u003C{ token: string; api_key: string }> {\n  return await $api(endpoints.token, { method: \"POST\" })\n}\n\nexport async function inboxCreateChannel(userId: string): Promise\u003C{ channel_id: string }> {\n  return await $api(endpoints.channel, {\n    method: \"POST\",\n    body: { user_id: userId },\n  })\n}\n","inbox.service.ts","typescript",[895,4607,4608,4652,4693,4697,4701,4738,4758,4773,4793,4800],{"__ignoreMap":728},[1086,4609,4610,4612,4615,4618,4621,4624,4627,4630,4632,4634,4637,4640,4643,4645,4647,4650],{"class":1088,"line":1089},[1086,4611,3625],{"class":1423},[1086,4613,4614],{"class":1155}," async",[1086,4616,4617],{"class":1155}," function",[1086,4619,4620],{"class":1105}," inboxGetToken",[1086,4622,4623],{"class":1146},"():",[1086,4625,4626],{"class":1092}," Promise",[1086,4628,4629],{"class":1146},"\u003C{",[1086,4631,4256],{"class":4109},[1086,4633,1133],{"class":1146},[1086,4635,4636],{"class":1092}," string",[1086,4638,4639],{"class":1146},";",[1086,4641,4642],{"class":4109}," api_key",[1086,4644,1133],{"class":1146},[1086,4646,4636],{"class":1092},[1086,4648,4649],{"class":1146}," }>",[1086,4651,1164],{"class":1146},[1086,4653,4654,4657,4660,4663,4665,4668,4670,4672,4674,4676,4679,4681,4683,4686,4688,4691],{"class":1088,"line":729},[1086,4655,4656],{"class":1423},"  return",[1086,4658,4659],{"class":1423}," await",[1086,4661,4662],{"class":1105}," $api",[1086,4664,1398],{"class":4109},[1086,4666,4667],{"class":1436},"endpoints",[1086,4669,861],{"class":1146},[1086,4671,4249],{"class":1436},[1086,4673,1227],{"class":1146},[1086,4675,4520],{"class":1146},[1086,4677,4678],{"class":4109}," method",[1086,4680,1133],{"class":1146},[1086,4682,1195],{"class":1146},[1086,4684,4685],{"class":1096},"POST",[1086,4687,1159],{"class":1146},[1086,4689,4690],{"class":1146}," }",[1086,4692,1455],{"class":4109},[1086,4694,4695],{"class":1088,"line":1112},[1086,4696,1291],{"class":1146},[1086,4698,4699],{"class":1088,"line":1181},[1086,4700,3390],{"emptyLinePlaceholder":738},[1086,4702,4703,4705,4707,4709,4712,4714,4717,4719,4721,4724,4726,4728,4730,4732,4734,4736],{"class":1088,"line":1205},[1086,4704,3625],{"class":1423},[1086,4706,4614],{"class":1155},[1086,4708,4617],{"class":1155},[1086,4710,4711],{"class":1105}," inboxCreateChannel",[1086,4713,1398],{"class":1146},[1086,4715,4716],{"class":1401},"userId",[1086,4718,1133],{"class":1146},[1086,4720,4636],{"class":1092},[1086,4722,4723],{"class":1146},"):",[1086,4725,4626],{"class":1092},[1086,4727,4629],{"class":1146},[1086,4729,4515],{"class":4109},[1086,4731,1133],{"class":1146},[1086,4733,4636],{"class":1092},[1086,4735,4649],{"class":1146},[1086,4737,1164],{"class":1146},[1086,4739,4740,4742,4744,4746,4748,4750,4752,4754,4756],{"class":1088,"line":1276},[1086,4741,4656],{"class":1423},[1086,4743,4659],{"class":1423},[1086,4745,4662],{"class":1105},[1086,4747,1398],{"class":4109},[1086,4749,4667],{"class":1436},[1086,4751,861],{"class":1146},[1086,4753,4501],{"class":1436},[1086,4755,1227],{"class":1146},[1086,4757,1164],{"class":1146},[1086,4759,4760,4763,4765,4767,4769,4771],{"class":1088,"line":1282},[1086,4761,4762],{"class":4109},"    method",[1086,4764,1133],{"class":1146},[1086,4766,1195],{"class":1146},[1086,4768,4685],{"class":1096},[1086,4770,1159],{"class":1146},[1086,4772,1202],{"class":1146},[1086,4774,4775,4778,4780,4782,4785,4787,4790],{"class":1088,"line":1288},[1086,4776,4777],{"class":4109},"    body",[1086,4779,1133],{"class":1146},[1086,4781,4520],{"class":1146},[1086,4783,4784],{"class":4109}," user_id",[1086,4786,1133],{"class":1146},[1086,4788,4789],{"class":1436}," userId",[1086,4791,4792],{"class":1146}," },\n",[1086,4794,4795,4798],{"class":1088,"line":2685},[1086,4796,4797],{"class":1146},"  }",[1086,4799,1455],{"class":4109},[1086,4801,4802],{"class":1088,"line":2700},[1086,4803,1291],{"class":1146},[842,4805,4806,4807,1589,4810,4813],{},"The chat component listens for ",[895,4808,4809],{},"message.new",[895,4811,4812],{},"message.read"," events from Stream, updates the UI in real time, and tracks unread counts per conversation.",[842,4815,4816,4819],{},[996,4817,4818],{},"No database models needed."," Stream stores all messages, handles delivery, and manages state. The Django backend has zero chat-related database tables.",[1074,4821,4823],{"id":4822},"what-we-shipped-in-one-sprint","What we shipped in one sprint",[1045,4825,4827,4832,4837,4842,4847,4852],{"className":4826},[1048,1049,1050,1051,1052],[1054,4828],{"description":4829,"icon":4830,"title":4831},"1-to-1 conversations between any two users on the platform.","i-lucide-message-square","Direct Messaging",[1054,4833],{"description":4834,"icon":4835,"title":4836},"Message delivery and read status, updated in real time.","i-lucide-check-check","Read Receipts",[1054,4838],{"description":4839,"icon":4840,"title":4841},"Badge counts per conversation and a global unread indicator.","i-lucide-bell-dot","Unread Counts",[1054,4843],{"description":4844,"icon":4845,"title":4846},"List of all available users with online/offline status.","i-lucide-users","User Roster",[1054,4848],{"description":4849,"icon":4850,"title":4851},"Arrow keys to switch between conversations for power users.","i-lucide-keyboard","Keyboard Navigation",[1054,4853],{"description":4854,"icon":4855,"title":4856},"Split-panel on desktop, slideover on mobile. Same functionality everywhere.","i-lucide-smartphone","Responsive UI",[863,4858,4860],{"id":4859},"when-does-this-approach-make-sense","When does this approach make sense?",[842,4862,4863],{},"This build-vs-buy decision is clear-cut in most cases:",[842,4865,4866],{},[996,4867,4868],{},"Use a managed service when:",[958,4870,4871,4874,4877,4880],{},[961,4872,4873],{},"Chat is a feature in your app, not the core product",[961,4875,4876],{},"You need it reliable and production-ready from day one",[961,4878,4879],{},"Your engineering budget should go toward what makes your product unique",[961,4881,4882],{},"You expect to scale without rebuilding infrastructure",[842,4884,4885],{},[996,4886,4887],{},"Consider building custom when:",[958,4889,4890,4893,4896],{},[961,4891,4892],{},"Messaging IS your core product (you're building the next Slack)",[961,4894,4895],{},"You have very specific compliance requirements that no provider can meet",[961,4897,4898],{},"You have a dedicated team to maintain it long-term",[842,4900,4901],{},"For most platforms we build at MusicTech Lab, the choice is obvious. Our clients' budgets should go toward the features that set their product apart, not toward reinventing infrastructure that already exists.",[863,4903,4905],{"id":4904},"key-takeaways","Key Takeaways",[1045,4907,4909,4913,4918,4922],{"className":4908},[1048,1049,1765,1051,1052],[1054,4910],{"description":4911,"icon":3847,"title":4912},"Real-time messaging involves WebSockets, delivery guarantees, storage, push notifications, and scaling. It's a product, not a feature.","Chat Is Not a Simple Feature",[1054,4914],{"description":4915,"icon":4916,"title":4917},"Services like Stream give you years of engineering through a single API. Your budget should go toward what makes your product unique.","i-lucide-shopping-bag","Buy, Don't Build",[1054,4919],{"description":4920,"icon":2939,"title":4921},"We shipped direct messaging with read receipts, unread counts, and responsive UI in two weeks using Stream Chat + Django + Nuxt.","One Sprint Integration",[1054,4923],{"description":4924,"icon":2895,"title":4925},"No chat-related tables in your database. Stream handles all message storage, delivery, and state management.","Zero Database Overhead",[863,4927,4929],{"id":4928},"the-bottom-line","The bottom line",[842,4931,4932],{},"Chat is one of those features where the gap between \"looks simple\" and \"is simple\" is enormous. A managed service like Stream closes that gap. You get a production-grade messaging system integrated in days, not months.",[842,4934,4935],{},"We've used this approach across multiple projects, and it works every time. The client gets real-time messaging that just works. We get to spend our time on the features that actually matter for their business.",[4937,4938],"hr",{},[842,4940,4941],{},[964,4942,4943,4944,4949],{},"Planning an app with real-time features like chat, notifications, or live updates? ",[846,4945,4948],{"href":4946,"rel":4947},"https://www.musictechlab.io/contact",[850],"Let's talk"," about the right building blocks for your project.",[1680,4951,4952],{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .s5tWE, html code.shiki .s5tWE{--shiki-light:#E53935;--shiki-light-font-style:italic;--shiki-default:#F07178;--shiki-default-font-style:italic;--shiki-dark:#F07178;--shiki-dark-font-style:italic}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":728,"searchDepth":729,"depth":729,"links":4954},[4955,4956,4957,4958,4963,4964,4965],{"id":3876,"depth":729,"text":3877},{"id":3886,"depth":729,"text":3887},{"id":3931,"depth":729,"text":3932},{"id":3973,"depth":729,"text":3974,"children":4959},[4960,4961,4962],{"id":3985,"depth":1112,"text":3986},{"id":4009,"depth":1112,"text":4010},{"id":4822,"depth":1112,"text":4823},{"id":4859,"depth":729,"text":4860},{"id":4904,"depth":729,"text":4905},{"id":4928,"depth":729,"text":4929},"2026-04-02T00:00:00.000Z","In-app messaging sounds like a simple feature, but building it from scratch is a project on its own. Here's why we use managed chat services and how we integrated one in a single sprint.",[4969,4972,4975],{"question":4970,"answer":4971},"How long does it take to add chat to an existing app?","Using a managed service like Stream Chat, a working integration with direct messaging, read receipts, and unread counts can be done in a single two-week sprint.",{"question":4973,"answer":4974},"Why not build chat from scratch?","Real-time messaging involves WebSocket infrastructure, message ordering, delivery guarantees, offline handling, and scaling. It's a standalone product, not a feature. Managed services give you years of engineering through a single API.",{"question":4976,"answer":4977},"What is Stream Chat?","Stream Chat (getstream.io) is a managed messaging API that provides real-time chat infrastructure. You integrate it into your app via SDKs, and Stream handles the hard parts: delivery, storage, real-time sync, and scaling.",{"src":4979},"/images/blog/why-we-dont-build-chat-from-scratch.webp",{},{"title":4982,"description":4983},"Why We Don't Build Chat From Scratch | MusicTech Lab","How we added real-time messaging to a Django + Nuxt app using Stream Chat in one sprint. Build vs buy for in-app chat.",[4985,4986,4987,4988,4989,4990],"stream-chat","real-time","django","nuxt","integration","case-study","Bl7ZxAPBhJZuRu_kxuB2LMG4hUkC76QCIaoV-sS_Czo",{"id":4993,"title":66,"authors":4994,"badge":4997,"body":4998,"category":4990,"client":723,"date":5483,"description":5484,"extension":734,"faq":5485,"featured":69,"featuredOrder":723,"hidden":69,"image":5498,"keyTakeaways":5500,"meta":5513,"navigation":738,"path":67,"seo":5514,"status":723,"stem":68,"tags":5517,"teaser":723,"__hash__":5524},"posts/blog/case-study/why-merch-is-the-new-royalty-check.md",[4995],{"name":834,"to":720,"avatar":4996},{"src":722},{"label":5,"color":3237},{"type":725,"value":4999,"toc":5472},[5000,5009,5013,5016,5088,5095,5098,5103,5107,5114,5117,5126,5144,5150,5156,5160,5163,5170,5186,5193,5197,5200,5203,5206,5232,5235,5241,5245,5248,5251,5271,5274,5279,5283,5290,5297,5302,5306,5309,5387,5389,5392,5395,5397,5406,5410],[842,5001,5002,5003,5008],{},"Selling one $35 band T-shirt generates the same revenue as ",[846,5004,5007],{"href":5005,"rel":5006},"https://royaltyexchange.com/blog/how-music-streaming-platforms-calculate-payouts-per-stream-2025",[850],"8,750 Spotify streams",". That single number explains why the smartest people in the music industry are rethinking where artist revenue actually comes from.",[863,5010,5012],{"id":5011},"the-royalty-math-that-doesnt-work","The royalty math that doesn't work",[842,5014,5015],{},"Let's start with what streaming actually pays. These are real per-stream rates from 2024-2025:",[871,5017,5018,5031],{},[874,5019,5020],{},[877,5021,5022,5025,5028],{},[880,5023,5024],{},"Platform",[880,5026,5027],{},"Per-Stream Payout",[880,5029,5030],{},"Per 1,000 Streams",[887,5032,5033,5044,5055,5066,5077],{},[877,5034,5035,5038,5041],{},[892,5036,5037],{},"Tidal",[892,5039,5040],{},"$0.012 - $0.015",[892,5042,5043],{},"~$12.84",[877,5045,5046,5049,5052],{},[892,5047,5048],{},"Apple Music",[892,5050,5051],{},"$0.007 - $0.01",[892,5053,5054],{},"~$6.20",[877,5056,5057,5060,5063],{},[892,5058,5059],{},"Amazon Music",[892,5061,5062],{},"$0.004 - $0.008",[892,5064,5065],{},"~$8.80",[877,5067,5068,5071,5074],{},[892,5069,5070],{},"Spotify",[892,5072,5073],{},"$0.003 - $0.005",[892,5075,5076],{},"~$3.00",[877,5078,5079,5082,5085],{},[892,5080,5081],{},"YouTube Music",[892,5083,5084],{},"$0.002 - $0.005",[892,5086,5087],{},"varies",[842,5089,5090,5091,5094],{},"At Spotify's rate, an artist needs roughly ",[996,5092,5093],{},"350,000 streams per month"," just to earn a U.S. minimum wage. That puts streaming income out of reach for the vast majority of working musicians.",[842,5096,5097],{},"Meanwhile, streaming accounts for 69% of total recorded music revenue globally. In 2024, that was $20.4 billion out of $29.6 billion (IFPI). The money is enormous at the platform level, but it gets diluted through labels, distributors, and publishers before it reaches artists.",[1572,5099,5100],{},[842,5101,5102],{},"Apple Music's per-stream rate is roughly 2x Spotify's, primarily because every Apple Music listener is a paying subscriber. There is no free tier diluting the pool.",[863,5104,5106],{"id":5105},"merch-the-8x-multiplier","Merch: the 8x multiplier",[842,5108,5109,5110,5113],{},"Here's where the economics flip. According to atVenu, the industry-standard merch analytics platform, the average small to mid-cap artist makes ",[996,5111,5112],{},"8x more per show in gross merchandise sales"," than a year's worth of streaming royalties.",[842,5115,5116],{},"That's not a typo. One show's merch table versus twelve months of streams.",[842,5118,5119,5120,5125],{},"The ",[846,5121,5124],{"href":5122,"rel":5123},"https://www.atvenu.com/post/an-updated-look-at-artist-merch-trends",[850],"numbers behind this"," are straightforward:",[958,5127,5128,5135,5138,5141],{},[961,5129,5130,5131,5134],{},"Average merch ",[996,5132,5133],{},"revenue per head"," at live shows is $10.29 in 2025, up 14% from 2024",[961,5136,5137],{},"Average item prices increased 9% year over year",[961,5139,5140],{},"A single $35 T-shirt equals ~8,750 Spotify streams in artist revenue",[961,5142,5143],{},"A $60 hoodie equals ~15,000 streams",[842,5145,5146,5147,861],{},"And unlike streaming, where revenue depends on algorithmic placement and playlist inclusion, merch revenue is driven by something labels can't easily replicate: ",[996,5148,5149],{},"direct fan connection",[842,5151,5152],{},[1027,5153],{"alt":5154,"src":5155},"Fan browsing merch at a live event","/images/blog/musictechlab_blog_merch-browsing.webp",[863,5157,5159],{"id":5158},"the-fastest-growing-segment-in-recorded-music","The fastest-growing segment in recorded music",[842,5161,5162],{},"This isn't just anecdotal. The industry data backs it up.",[842,5164,5165,5166,5169],{},"IFPI's Global Music Report shows that \"expanded rights\" - which includes merch, licensing, and fan experiences - grew ",[996,5167,5168],{},"21.5% in 2025",". That makes it the fastest-growing segment in the entire recorded music industry, outpacing streaming growth significantly.",[1045,5171,5173,5178,5182],{"className":5172},[1048,1049,1050,1051,1052],[1054,5174],{"description":5175,"icon":5176,"title":5177},"Global merchandise market forecast by MIDiA Research, including physical merch, digital merch, and physical music.","i-lucide-shirt","$16.3B by 2030",[1054,5179],{"description":5180,"icon":3920,"title":5181},"Expanded rights (merch + licensing + fan experiences) grew 21.5% in 2025 - the fastest-growing segment in recorded music.","21.5% Growth",[1054,5183],{"description":5184,"icon":4845,"title":5185},"Goldman Sachs estimates an additional $4.3 billion annually from superfan monetization, assuming 20% of paid subscribers spend 2x average.","$4.3B Superfan Opportunity",[842,5187,5188,5189,5192],{},"Goldman Sachs projects global music revenues will ",[996,5190,5191],{},"double to $200 billion by 2035",", with superfan monetization identified as a key growth driver. Their \"Music in the Air\" report estimates the superfan opportunity alone at $4.3 billion annually.",[863,5194,5196],{"id":5195},"fan-designed-merch-the-model-that-changes-everything","Fan-designed merch: the model that changes everything",[842,5198,5199],{},"Traditional merch has a problem: artists (or their labels) design it, manufacture it in bulk, ship it to warehouses, and hope it sells. That means upfront capital, inventory risk, and unsold stock sitting in boxes.",[842,5201,5202],{},"Fan-designed merch flips this model. Fans create designs. Artists approve them. Both parties earn from sales. And nothing gets manufactured until someone actually buys it.",[842,5204,5205],{},"This is the model that a growing number of platforms are building. The economics are compelling:",[958,5207,5208,5214,5220,5226],{},[961,5209,5210,5213],{},[996,5211,5212],{},"Zero inventory risk"," - print-on-demand means no unsold stock",[961,5215,5216,5219],{},[996,5217,5218],{},"Community engagement"," - fans become co-creators, not just consumers",[961,5221,5222,5225],{},[996,5223,5224],{},"Scalable catalog"," - hundreds of designs without hundreds of SKUs in a warehouse",[961,5227,5228,5231],{},[996,5229,5230],{},"Global fulfillment"," - POD networks handle printing and shipping worldwide",[842,5233,5234],{},"The global print-on-demand market is now valued at $11-13 billion and growing at 23-26% CAGR. The infrastructure exists. The question is how to connect it to artist storefronts seamlessly.",[842,5236,5237],{},[1027,5238],{"alt":5239,"src":5240},"Direct-to-film printing process for on-demand merch","/images/blog/musictechlab_blog_pod-printing.webp",[863,5242,5244],{"id":5243},"the-tech-stack-that-makes-it-work","The tech stack that makes it work",[842,5246,5247],{},"This is where things get practical. For fan-designed merch to work at scale, you need a pipeline that connects the design platform (where fans create and artists approve) to the storefront (where fans buy) to the fulfillment network (where products get made and shipped).",[842,5249,5250],{},"At MusicTech Lab, we're building exactly this kind of bridge. Our Shopify integration connects product catalogs from merch platforms directly to an artist's Shopify store:",[991,5252,5253,5259,5265],{},[961,5254,5255,5258],{},[996,5256,5257],{},"Product sync"," - approved designs are automatically pushed to Shopify with correct variants (sizes, colors), pricing, and images",[961,5260,5261,5264],{},[996,5262,5263],{},"Order routing"," - when a fan purchases merch, the order data flows back to the design platform for fulfillment",[961,5266,5267,5270],{},[996,5268,5269],{},"Inventory-free catalog"," - since everything is print-on-demand, the Shopify store reflects available designs without stock management",[842,5272,5273],{},"The key technical challenge is mapping between two different product models. A design platform thinks in terms of \"approved designs with print areas.\" Shopify thinks in terms of \"products with variants and SKUs.\" The sync layer translates between them, handling variant explosion (one design x multiple sizes x multiple colors = many Shopify variants) and keeping everything in sync as designs are added, updated, or removed.",[1032,5275,5276],{},[842,5277,5278],{},"The same architecture works for any artist-to-storefront integration, not just fan-designed merch. Any catalog that lives outside Shopify - whether it's a label's product database, a print-on-demand API, or a custom merch platform - can use this pattern to keep storefronts automatically in sync.",[863,5280,5282],{"id":5281},"the-risk-fan-fatigue","The risk: fan fatigue",[842,5284,5285,5286,5289],{},"MIDiA Research found that ",[996,5287,5288],{},"39% of fans feel their fandom is being exploited",", and nearly half say merch is becoming unaffordable. As Mark Mulligan from MIDiA puts it: \"You cannot harvest fandom if you are not also nurturing it.\"",[842,5291,5292,5293,5296],{},"The takeaway: merch works best as a ",[996,5294,5295],{},"relationship channel",", not a revenue extraction tool. Fan co-creation helps here - participants spend more willingly than passive buyers.",[1901,5298,5299],{},[842,5300,5301],{},"Amazon accounts for 39% of physical music merch purchases. Selling only through your own store leaves significant demand on the table. A multi-channel approach (own store + marketplaces) captures more of the market.",[863,5303,5305],{"id":5304},"what-this-means-for-labels-managers-and-artists","What this means for labels, managers, and artists",[842,5307,5308],{},"The shift from streaming royalties to merch as a primary revenue driver isn't a trend. It's a structural change in how the music economy works. Here's what to do about it:",[1045,5310,5312,5337,5362],{"className":5311},[1048,1049,1050,1051,1052],[1054,5313,5317],{"description":5314,"icon":5315,"title":5316},"Turn your merch into a revenue engine.","i-lucide-mic","For Artists",[958,5318,5319,5325,5331],{},[961,5320,5321,5324],{},[996,5322,5323],{},"Treat merch like your release calendar."," Regular drops, seasonal collections, and limited editions keep fans engaged",[961,5326,5327,5330],{},[996,5328,5329],{},"Consider fan co-creation."," Fans submit designs, you approve - a creative team with zero payroll",[961,5332,5333,5336],{},[996,5334,5335],{},"Go multi-channel."," Own Shopify store for margins, marketplace presence for reach",[1054,5338,5342],{"description":5339,"icon":5340,"title":5341},"Optimize the merch operation.","i-lucide-briefcase","For Managers & Labels",[958,5343,5344,5350,5356],{},[961,5345,5346,5349],{},[996,5347,5348],{},"Track revenue per head."," The $10.29 average RPH in 2025 is a benchmark - if you're below it, there's room to grow",[961,5351,5352,5355],{},[996,5353,5354],{},"Invest in the tech stack."," Automated catalog sync is infrastructure, not a nice-to-have",[961,5357,5358,5361],{},[996,5359,5360],{},"Watch the affordability signal."," 39% of fans feel exploited - offer a range from $15 accessories to $60 premium items",[1054,5363,5367],{"description":5364,"icon":5365,"title":5366},"Build the connective tissue.","i-lucide-code","For Music Tech Founders",[958,5368,5369,5375,5381],{},[961,5370,5371,5374],{},[996,5372,5373],{},"The integration layer is the opportunity."," Shopify, POD networks, and design tools exist - the glue between them is missing",[961,5376,5377,5380],{},[996,5378,5379],{},"Think beyond apparel."," Home decor is the fastest-growing POD category in 2026",[961,5382,5383,5386],{},[996,5384,5385],{},"Build for the long tail."," Millions of mid-tier artists have no merch operation at all",[863,5388,4929],{"id":4928},[842,5390,5391],{},"Streaming built the modern music industry's distribution layer. But it didn't solve the artist revenue problem. Merch - especially tech-enabled, fan-driven, zero-inventory merch - is the strongest candidate to fill that gap.",[842,5393,5394],{},"One T-shirt equals 8,750 streams. One engaged fan community designing merch equals a sustainable revenue engine. The math has never been clearer.",[4937,5396],{},[1032,5398,5399],{},[842,5400,5401,5402,861],{},"At MusicTech Lab, we build the technology that connects music platforms to storefronts. If you're working on artist merch, direct-to-fan commerce, or Shopify integrations for the music industry, ",[846,5403,5405],{"href":4946,"rel":5404},[850],"let's talk",[863,5407,5409],{"id":5408},"sources","Sources",[958,5411,5412,5419,5426,5433,5441,5448,5456,5464],{},[961,5413,5414,5418],{},[846,5415,5417],{"href":5005,"rel":5416},[850],"How Music Streaming Platforms Calculate Payouts Per Stream (2025)"," - Royalty Exchange",[961,5420,5421,5425],{},[846,5422,5424],{"href":5122,"rel":5423},[850],"An Updated Look at Artist Merch Trends in 2025"," - atVenu",[961,5427,5428,5425],{},[846,5429,5432],{"href":5430,"rel":5431},"https://www.atvenu.com/post/how-much-money-artists-make-in-streaming-vs-merchandise-sales",[850],"How Much Money Artists Make in Streaming vs. Merchandise Sales",[961,5434,5435,5440],{},[846,5436,5439],{"href":5437,"rel":5438},"https://www.midiaresearch.com/blog/global-merchandise-market-to-hit-163-billion-by-2030-but-annual-growth-slows-to-16",[850],"Global Merchandise Market to Hit $16.3 Billion by 2030"," - MIDiA Research",[961,5442,5443,5440],{},[846,5444,5447],{"href":5445,"rel":5446},"https://www.midiaresearch.com/blog/are-we-reaching-peak-fandom",[850],"Are We Reaching Peak Fandom?",[961,5449,5450,5455],{},[846,5451,5454],{"href":5452,"rel":5453},"https://www.ifpi.org/ifpi-amidst-highly-competitive-market-global-recorded-music-revenues-grew-4-8-in-2024/",[850],"IFPI Global Music Report: Revenues Grew 4.8% in 2024"," - IFPI",[961,5457,5458,5463],{},[846,5459,5462],{"href":5460,"rel":5461},"https://www.musicbusinessworldwide.com/global-recorded-music-revenues-hit-31-7bn-in-2025-up-6-4-yoy-users-of-paid-music-subscriptions-reach-837m/",[850],"Global Recorded Music Revenues Hit $31.7B in 2025"," - Music Business Worldwide",[961,5465,5466,5471],{},[846,5467,5470],{"href":5468,"rel":5469},"https://www.goldmansachs.com/insights/articles/global-music-revenues-are-forecast-to-double-to-200-million-in-2035",[850],"Global Music Revenues Forecast to Double to $200B by 2035"," - Goldman Sachs",{"title":728,"searchDepth":729,"depth":729,"links":5473},[5474,5475,5476,5477,5478,5479,5480,5481,5482],{"id":5011,"depth":729,"text":5012},{"id":5105,"depth":729,"text":5106},{"id":5158,"depth":729,"text":5159},{"id":5195,"depth":729,"text":5196},{"id":5243,"depth":729,"text":5244},{"id":5281,"depth":729,"text":5282},{"id":5304,"depth":729,"text":5305},{"id":4928,"depth":729,"text":4929},{"id":5408,"depth":729,"text":5409},"2026-03-27T00:00:00.000Z","Streaming pays fractions of a cent. Merch pays the bills. Here's how the economics of artist revenue are shifting, and why technology is the key enabler.",[5486,5489,5492,5495],{"question":5487,"answer":5488},"How much does one Spotify stream pay an artist?","In 2024-2025, Spotify pays approximately $0.003 to $0.005 per stream. An artist needs roughly 8,750 streams to match the revenue from selling a single $35 T-shirt.",{"question":5490,"answer":5491},"How big is the global music merch market?","MIDiA Research forecasts the global merchandise market will reach $16.3 billion by 2030. Expanded rights including merch, licensing, and fan experiences grew 21.5% in 2025, making it the fastest-growing segment in recorded music.",{"question":5493,"answer":5494},"What is fan-designed merch?","Fan-designed merch is a model where fans create designs, artists approve them, and both parties earn from sales. It combines community engagement with zero-inventory economics through print-on-demand fulfillment.",{"question":5496,"answer":5497},"How does print-on-demand work for music merch?","Print-on-demand eliminates upfront inventory risk. Products are only manufactured when a customer places an order. The global POD market is valued at $11-13 billion and growing at 23-26% CAGR, making it increasingly viable for artists at any scale.",{"src":5499},"/images/blog/musictechlab_blog_why-merch-is-the-new-royalty-check.webp",{"enabled":738,"items":5501},[5502,5505,5508,5510],{"text":5503,"icon":5504},"One $35 T-shirt = 8,750 Spotify streams. Merch is structurally more efficient than streaming royalties.","i-lucide-dollar-sign",{"text":5506,"icon":5507},"Fan-designed merch eliminates inventory risk and turns fans into co-creators via print-on-demand.","i-lucide-palette",{"text":5509,"icon":5365},"The key tech challenge is the integration layer: design platforms, Shopify, POD fulfillment, with variant explosion across sizes and colors.",{"text":5511,"icon":5512},"39% of fans feel their fandom is exploited. Merch works best as a relationship channel, not extraction.","i-lucide-heart",{},{"title":5515,"description":5516},"Why Merch Is the New Royalty Check | MusicTech Lab","One T-shirt equals 8,750 Spotify streams. Learn how merch is becoming the primary revenue engine for artists and what tech makes it possible.",[5518,3192,5519,5520,5521,5522,5523],"merch","streaming","direct-to-fan","shopify","print-on-demand","musictech","bjzkkYJi2abyLeSKwKkLCq4fHM0rqbYBeqlXsKPxGos",{"id":5526,"title":297,"authors":5527,"badge":5533,"body":5536,"category":5678,"client":723,"date":5483,"description":5679,"extension":734,"faq":723,"featured":738,"featuredOrder":3515,"hidden":69,"image":5680,"keyTakeaways":723,"meta":5682,"navigation":738,"path":298,"seo":5683,"status":723,"stem":299,"tags":5684,"teaser":723,"__hash__":5686},"posts/blog/newsletter/musictech-insights-9-curated-by-yaw-asamani.md",[5528],{"name":5529,"to":5530,"avatar":5531},"Yaw Asamani","https://www.linkedin.com/in/yawasamani/",{"src":5532},"/images/partners/musictechlab_partners_yaw_asamani.webp",{"label":5534,"color":5535},"#9","#6B9B37",{"type":725,"value":5537,"toc":5670},[5538,5543,5556,5559,5561,5564,5569,5574,5579,5590,5592,5596,5599,5606,5608,5612,5615,5621,5623,5627,5630,5636,5638,5642,5645,5651,5653],[5539,5540,5542],"h1",{"id":5541},"musictech-insights-9-curated-by-yaw-asamani","MusicTech Insights #9 | Curated by Yaw Asamani",[842,5544,5545,5546,5549,5550,5555],{},"This issue is guest‑curated by ",[846,5547,5529],{"href":5530,"rel":5548},[850],", founder of ",[846,5551,5554],{"href":5552,"rel":5553},"https://www.linkedin.com/company/mudder-fracas/",[850],"Mudder Fracas"," (a content house on creative culture), and an entrepreneur focused on music startups and music businesses. Yaw has spent years observing how artists connect with fans and how technology can reshape these relationships.",[842,5557,5558],{},"In this edition, he shares how direct‑to‑fan monetization is finally moving from theory to real, scalable models that change how the industry thinks about superfandom and engagement.",[4937,5560],{},[863,5562,297],{"id":5563},"music-business-meets-direct-to-fan",[842,5565,5566],{},[964,5567,5568],{},"Hi, I’m Yaw.",[842,5570,5571],{},[964,5572,5573],{},"We’ve been talking about Direct-to-Fan for years, but until recently there wasn’t a clear model that worked at scale. Now, major players are betting on fan engagement infrastructure as a core part of the music business.",[842,5575,5576],{},[964,5577,5578],{},"Below are three stories worth reading that show how and why this is happening and what it might mean for artists, labels, and musictech.",[1045,5580,5582],{"style":5581},"position: relative; width: 100%; padding-bottom: 56.25%; height: 0; overflow: hidden;",[5583,5584],"iframe",{"style":5585,"src":5586,"title":5587,"frameBorder":4417,"allow":5588,"referrerPolicy":5589,"allowFullScreen":738},"position: absolute; top: 0; left: 0; width: 100%; height: 100%;","https://www.youtube.com/embed/-7W_HkjrHi4","MusicTech Insights #8 | Music Business Meets Direct-to-Fan","accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share","strict-origin-when-cross-origin",[4937,5591],{},[1074,5593,5595],{"id":5594},"universal-music-group-strikes-deal-with-superfan-platform-even","Universal Music Group strikes deal with superfan platform EVEN",[842,5597,5598],{},"Universal Music Group has signed a multi‑year agreement with direct‑to‑fan platform EVEN to power D2C sales, early access, exclusive merch, and community engagement tools for artists worldwide. This is a strong signal that major labels see fan relationships as essential infrastructure rather than an afterthought.",[842,5600,5601],{},[846,5602,5605],{"href":5603,"rel":5604},"https://www.universalmusic.com/even-announces-agreement-with-universal-music-group-to-provide-direct-to-fan-platform-for-artists-worldwide/",[850],"Learn more",[4937,5607],{},[1074,5609,5611],{"id":5610},"independent-artists-prove-the-model-works","Independent artists prove the model works",[842,5613,5614],{},"Independent artist case studies show that superfandom engagement can create real revenue beyond streaming alone. For example, hip‑hop artist LaRussell generated over $100,000 in direct sales by offering early access and exclusive content to superfans before his music hit streaming platforms, followed by a significant uplift in streams when it launched publicly. This type of “windowed release” shows how deep fan relationships can translate into sustainable income and chart impact.",[842,5616,5617],{},[846,5618,5605],{"href":5619,"rel":5620},"https://learn.andrmusic.co/en/articles/13545916-larussell-case-study-100k-from-direct-fan-sales-via-even?utm_source=chatgpt.com",[850],[4937,5622],{},[1074,5624,5626],{"id":5625},"hybrid-release-strategies-reward-both-fans-and-discovery","Hybrid release strategies reward both fans and discovery",[842,5628,5629],{},"Modern release playbooks are shifting toward hybrid strategies that blend direct‑to‑fan commerce with streaming promotion. Artists can build early momentum with fans by offering direct access and premium experiences first, while still leveraging DSPs for discovery and algorithmic reach. This fan‑first release model helps artists own their audience and maximize both engagement and long‑term growth.",[842,5631,5632],{},[846,5633,5605],{"href":5634,"rel":5635},"https://music.loop.fans/learn/the-fan-first-release-strategy-a-new-playbook-for-artist-empowerment-in-the-streaming-economy",[850],[4937,5637],{},[1074,5639,5641],{"id":5640},"fandom-is-becoming-an-asset-class-in-music","Fandom Is Becoming an Asset Class in Music",[842,5643,5644],{},"A recent industry analysis by Hezekiah McCaskill argues that fandom is no longer just a marketing tactic but a core economic asset in 2026. With streaming focused on scale, the industry is shifting toward direct engagement that rewards loyalty and deeper relationships.",[842,5646,5647],{},[846,5648,5605],{"href":5649,"rel":5650},"https://lbbonline.com/news/Fandom-Isnt-a-Marketing-Tactic-Its-an-Asset-Class",[850],[4937,5652],{},[842,5654,5655,5656,5660,5661,5660,5666,861],{},"Please also follow Mudder Fracas on the following platforms for content about Creative Culture meets entrepreneurship: ",[846,5657,5070],{"href":5658,"rel":5659},"https://open.spotify.com/show/3hO2sPxQwTjsu4UU8N0wgM?si=rsygDsmJQ2WYS0qzLfb82w&nd=1&dlsi=ff31804ac50a448d",[850],", ",[846,5662,5665],{"href":5663,"rel":5664},"https://www.instagram.com/mudderfracas?igsh=dzc4end4aWFxejJ1",[850],"Instagram",[846,5667,5669],{"href":5552,"rel":5668},[850],"LinkedIn",{"title":728,"searchDepth":729,"depth":729,"links":5671},[5672],{"id":5563,"depth":729,"text":297,"children":5673},[5674,5675,5676,5677],{"id":5594,"depth":1112,"text":5595},{"id":5610,"depth":1112,"text":5611},{"id":5625,"depth":1112,"text":5626},{"id":5640,"depth":1112,"text":5641},"newsletter","Handpicked articles, updates, and insights from the fast-moving world of music and entertainment technology, curated just for you.",{"src":5681},"/images/blog/musictechlab_blog_musictech-insights_9_yaw_asamani.webp",{},{"title":297,"description":5679},[5678,5520,5523,5685],"even","eEQphTomLx8-s849idDMtsvnqA-JFC1-Je22V1f7zok",{"id":5688,"title":84,"authors":5689,"badge":5692,"body":5693,"category":731,"client":723,"date":7472,"description":7473,"extension":734,"faq":7474,"featured":69,"featuredOrder":723,"hidden":69,"image":7487,"keyTakeaways":7489,"meta":7501,"navigation":738,"path":85,"seo":7502,"status":723,"stem":86,"tags":7505,"teaser":723,"__hash__":7509},"posts/blog/music-data/ai-powered-analytics-dashboard-django-clickhouse-ollama.md",[5690],{"name":834,"to":720,"avatar":5691},{"src":722},{"label":5,"color":3237},{"type":725,"value":5694,"toc":7457},[5695,5698,5701,5705,5708,5711,5728,5731,5736,5740,5743,5746,5760,5763,5857,5872,5876,5887,5890,6220,6223,6365,6368,6379,6383,6386,6389,6407,6410,6544,6547,6552,6556,6559,6565,7107,7110,7139,7145,7330,7337,7340,7345,7349,7352,7355,7358,7395,7399,7402,7406,7409,7413,7416,7420,7423,7427,7430,7444,7447,7451,7454],[842,5696,5697],{},"Your data team knows the answer is in the database. Your A&R lead, your finance director, and your label manager do not know how to get it out. This is the gap that costs music companies real money, not in licensing fees or infrastructure, but in decisions delayed, trends missed, and reports that arrive a week too late.",[842,5699,5700],{},"We built an AI-powered analytics dashboard to close that gap. It sits inside MusicData Lab, our royalty analytics platform, and it lets anyone with access type a question in plain English and get a chart back in seconds.",[863,5702,5704],{"id":5703},"the-problem-everybody-talks-about-at-conferences","The problem everybody talks about at conferences",[842,5706,5707],{},"The music industry has a data access problem. Not a data collection problem. Labels and distributors already have millions of rows of streaming data, royalty reports, and territorial breakdowns sitting in their databases. The bottleneck is getting from \"I need to know which retailers drove revenue last quarter\" to an actual answer.",[842,5709,5710],{},"Today, that journey typically looks like this:",[991,5712,5713,5716,5719,5722,5725],{},[961,5714,5715],{},"A business stakeholder writes an email or Slack message to the data team",[961,5717,5718],{},"The data team interprets the request, writes a SQL query, exports the results",[961,5720,5721],{},"Someone pastes the numbers into a spreadsheet and builds a chart",[961,5723,5724],{},"The chart goes back to the stakeholder, who asks a follow-up question",[961,5726,5727],{},"Repeat from step 1",[842,5729,5730],{},"This loop can take hours, sometimes days. Multiply it across every label, every territory, every reporting period, and you start to see the scale of the problem.",[1572,5732,5733],{},[842,5734,5735],{},"A 2024 IFPI report noted that the global recorded music market generated $28.6 billion in revenue, with streaming accounting for 67% of that. The volume of data behind those numbers is staggering, and growing every quarter.",[863,5737,5739],{"id":5738},"what-if-business-users-could-just-ask","What if business users could just ask?",[842,5741,5742],{},"That was the design question behind our AI Dashboard. Instead of routing every data request through a technical team, what if the platform could understand a question like \"top 5 artists from the US by income\" and return a bar chart?",[842,5744,5745],{},"The workflow is simple:",[991,5747,5748,5751,5754,5757],{},[961,5749,5750],{},"A user types a question in the chat interface",[961,5752,5753],{},"An LLM translates the question into a ClickHouse SQL query",[961,5755,5756],{},"The query runs against the analytics database (read-only, with safety guardrails)",[961,5758,5759],{},"Results come back as an interactive chart and a data table",[842,5761,5762],{},"No SQL knowledge required. No waiting for the data team. No spreadsheets.",[1013,5764,5766],{"className":3341,"code":5765,"language":3343,"meta":728,"style":728},"sequenceDiagram\n    participant U as User\n    participant UI as Chat UI\n    participant LLM as LLM (Ollama/Claude)\n    participant SG as SQL Guard\n    participant CH as ClickHouse\n    participant CB as Chart Builder\n\n    U->>UI: \"Top 5 artists by income\"\n    UI->>LLM: System prompt + question\n    LLM-->>UI: Generated SQL query\n    UI->>SG: Validate SQL\n    SG-->>UI: Sanitised query (SELECT only, LIMIT enforced)\n    UI->>CH: Execute (read-only, 10s timeout)\n    CH-->>UI: Result rows + columns\n    UI->>CB: Build chart config\n    CB-->>UI: Chart.js config (type, labels, datasets)\n    UI-->>U: Interactive chart + data table\n",[895,5767,5768,5773,5778,5783,5788,5793,5798,5803,5807,5812,5817,5822,5827,5832,5837,5842,5847,5852],{"__ignoreMap":728},[1086,5769,5770],{"class":1088,"line":1089},[1086,5771,5772],{"class":1436},"sequenceDiagram\n",[1086,5774,5775],{"class":1088,"line":729},[1086,5776,5777],{"class":1436},"    participant U as User\n",[1086,5779,5780],{"class":1088,"line":1112},[1086,5781,5782],{"class":1436},"    participant UI as Chat UI\n",[1086,5784,5785],{"class":1088,"line":1181},[1086,5786,5787],{"class":1436},"    participant LLM as LLM (Ollama/Claude)\n",[1086,5789,5790],{"class":1088,"line":1205},[1086,5791,5792],{"class":1436},"    participant SG as SQL Guard\n",[1086,5794,5795],{"class":1088,"line":1276},[1086,5796,5797],{"class":1436},"    participant CH as ClickHouse\n",[1086,5799,5800],{"class":1088,"line":1282},[1086,5801,5802],{"class":1436},"    participant CB as Chart Builder\n",[1086,5804,5805],{"class":1088,"line":1288},[1086,5806,3390],{"emptyLinePlaceholder":738},[1086,5808,5809],{"class":1088,"line":2685},[1086,5810,5811],{"class":1436},"    U->>UI: \"Top 5 artists by income\"\n",[1086,5813,5814],{"class":1088,"line":2700},[1086,5815,5816],{"class":1436},"    UI->>LLM: System prompt + question\n",[1086,5818,5819],{"class":1088,"line":3398},[1086,5820,5821],{"class":1436},"    LLM-->>UI: Generated SQL query\n",[1086,5823,5824],{"class":1088,"line":1715},[1086,5825,5826],{"class":1436},"    UI->>SG: Validate SQL\n",[1086,5828,5829],{"class":1088,"line":3409},[1086,5830,5831],{"class":1436},"    SG-->>UI: Sanitised query (SELECT only, LIMIT enforced)\n",[1086,5833,5834],{"class":1088,"line":3415},[1086,5835,5836],{"class":1436},"    UI->>CH: Execute (read-only, 10s timeout)\n",[1086,5838,5839],{"class":1088,"line":3421},[1086,5840,5841],{"class":1436},"    CH-->>UI: Result rows + columns\n",[1086,5843,5844],{"class":1088,"line":3427},[1086,5845,5846],{"class":1436},"    UI->>CB: Build chart config\n",[1086,5848,5849],{"class":1088,"line":3433},[1086,5850,5851],{"class":1436},"    CB-->>UI: Chart.js config (type, labels, datasets)\n",[1086,5853,5854],{"class":1088,"line":3439},[1086,5855,5856],{"class":1436},"    UI-->>U: Interactive chart + data table\n",[1045,5858,5860,5864,5868],{"className":5859},[1048,1049,1050,1051,1052],[1054,5861],{"description":5862,"icon":4830,"title":5863},"Ask questions in plain English. No SQL, no training, no onboarding friction.","Natural Language Input",[1054,5865],{"description":5866,"icon":2895,"title":5867},"Columnar storage handles millions of streaming records with sub-second query times.","ClickHouse Analytics",[1054,5869],{"description":5870,"icon":3844,"title":5871},"Results render as interactive charts. Bar, line, and doughnut, selected automatically.","Instant Visualization",[863,5873,5875],{"id":5874},"why-this-matters-for-music-companies-specifically","Why this matters for music companies specifically",[842,5877,5878,5879,5882,5883,5886],{},"Music royalty data is uniquely complex. A single label might receive reports from ",[846,5880,5881],{"href":77},"13 different distributors, each with its own file format",", column naming, and date conventions. Once that data is normalised and loaded into an analytics database, the schema reflects that complexity: dozens of fields covering artists, tracks, retailers, territories, currencies, and time periods. Even the ",[846,5884,5885],{"href":81},"retailer names need normalisation"," before they become queryable.",[842,5888,5889],{},"This is precisely the kind of dataset where AI-assisted querying shines. The system prompt includes the full database schema, domain-specific hints, and few-shot examples that teach the model how to write correct ClickHouse SQL. We build it dynamically from Django model metadata, so the prompt stays in sync with the schema automatically:",[1013,5891,5894],{"className":1368,"code":5892,"filename":5893,"language":1250,"meta":728,"style":728},"def build_system_prompt() -> str:\n    fields = []\n    for field in StreamDataCH._meta.get_fields():\n        fields.append(f\"  - {field.name} ({field.__class__.__name__})\")\n\n    schema_block = \"\\n\".join(fields)\n\n    return (\n        \"You are a SQL analyst for a music streaming analytics platform.\\n\"\n        f\"The database is ClickHouse. There is one table: `{TABLE}`.\\n\"\n        f\"\\n## Schema\\n\\n{schema_block}\\n\"\n        \"\\n## Domain hints\\n\\n\"\n        \"- `final_income` is income converted to the target currency\\n\"\n        \"- `retailer_union` is the normalized retailer name\\n\"\n        \"- Always filter out empty strings when grouping by text fields\\n\"\n        \"\\n## Output rules\\n\\n\"\n        \"1. Output ONLY a single SELECT statement\\n\"\n        \"2. Always include a LIMIT clause (max 100)\\n\"\n        \"3. Never use INSERT, UPDATE, DELETE, DROP, or any DDL\\n\"\n        f\"\\n## Examples\\n\\n{examples_block}\"\n    )\n","prompts.py",[895,5895,5896,5911,5921,5948,6001,6005,6031,6035,6042,6054,6076,6101,6114,6125,6136,6147,6160,6171,6182,6193,6215],{"__ignoreMap":728},[1086,5897,5898,5900,5903,5905,5907,5909],{"class":1088,"line":1089},[1086,5899,1392],{"class":1155},[1086,5901,5902],{"class":1105}," build_system_prompt",[1086,5904,2516],{"class":1146},[1086,5906,1413],{"class":1146},[1086,5908,1407],{"class":1092},[1086,5910,1418],{"class":1146},[1086,5912,5913,5916,5918],{"class":1088,"line":729},[1086,5914,5915],{"class":1436},"    fields ",[1086,5917,1440],{"class":1146},[1086,5919,5920],{"class":1146}," []\n",[1086,5922,5923,5926,5929,5932,5935,5937,5940,5942,5945],{"class":1088,"line":1112},[1086,5924,5925],{"class":1423},"    for",[1086,5927,5928],{"class":1436}," field ",[1086,5930,5931],{"class":1423},"in",[1086,5933,5934],{"class":1436}," StreamDataCH",[1086,5936,861],{"class":1146},[1086,5938,5939],{"class":4109},"_meta",[1086,5941,861],{"class":1146},[1086,5943,5944],{"class":1105},"get_fields",[1086,5946,5947],{"class":1146},"():\n",[1086,5949,5950,5953,5955,5958,5960,5963,5966,5968,5971,5973,5975,5977,5980,5982,5984,5986,5989,5991,5994,5996,5999],{"class":1088,"line":1181},[1086,5951,5952],{"class":1436},"        fields",[1086,5954,861],{"class":1146},[1086,5956,5957],{"class":1105},"append",[1086,5959,1398],{"class":1146},[1086,5961,5962],{"class":1155},"f",[1086,5964,5965],{"class":1096},"\"  - ",[1086,5967,4409],{"class":1187},[1086,5969,5970],{"class":1105},"field",[1086,5972,861],{"class":1146},[1086,5974,4184],{"class":4109},[1086,5976,4423],{"class":1187},[1086,5978,5979],{"class":1096}," (",[1086,5981,4409],{"class":1187},[1086,5983,5970],{"class":1105},[1086,5985,861],{"class":1146},[1086,5987,5988],{"class":1436},"__class__",[1086,5990,861],{"class":1146},[1086,5992,5993],{"class":1436},"__name__",[1086,5995,4423],{"class":1187},[1086,5997,5998],{"class":1096},")\"",[1086,6000,1455],{"class":1146},[1086,6002,6003],{"class":1088,"line":1205},[1086,6004,3390],{"emptyLinePlaceholder":738},[1086,6006,6007,6010,6012,6014,6017,6019,6021,6024,6026,6029],{"class":1088,"line":1276},[1086,6008,6009],{"class":1436},"    schema_block ",[1086,6011,1440],{"class":1146},[1086,6013,1195],{"class":1146},[1086,6015,6016],{"class":1436},"\\n",[1086,6018,1159],{"class":1146},[1086,6020,861],{"class":1146},[1086,6022,6023],{"class":1105},"join",[1086,6025,1398],{"class":1146},[1086,6027,6028],{"class":1105},"fields",[1086,6030,1455],{"class":1146},[1086,6032,6033],{"class":1088,"line":1282},[1086,6034,3390],{"emptyLinePlaceholder":738},[1086,6036,6037,6039],{"class":1088,"line":1288},[1086,6038,1460],{"class":1423},[1086,6040,6041],{"class":1146}," (\n",[1086,6043,6044,6047,6050,6052],{"class":1088,"line":2685},[1086,6045,6046],{"class":1146},"        \"",[1086,6048,6049],{"class":1096},"You are a SQL analyst for a music streaming analytics platform.",[1086,6051,6016],{"class":1436},[1086,6053,4441],{"class":1146},[1086,6055,6056,6059,6062,6064,6067,6069,6072,6074],{"class":1088,"line":2700},[1086,6057,6058],{"class":1155},"        f",[1086,6060,6061],{"class":1096},"\"The database is ClickHouse. There is one table: `",[1086,6063,4409],{"class":1187},[1086,6065,6066],{"class":1436},"TABLE",[1086,6068,4423],{"class":1187},[1086,6070,6071],{"class":1096},"`.",[1086,6073,6016],{"class":1436},[1086,6075,4441],{"class":1096},[1086,6077,6078,6080,6082,6084,6087,6090,6092,6095,6097,6099],{"class":1088,"line":3398},[1086,6079,6058],{"class":1155},[1086,6081,1159],{"class":1096},[1086,6083,6016],{"class":1436},[1086,6085,6086],{"class":1096},"## Schema",[1086,6088,6089],{"class":1436},"\\n\\n",[1086,6091,4409],{"class":1187},[1086,6093,6094],{"class":1436},"schema_block",[1086,6096,4423],{"class":1187},[1086,6098,6016],{"class":1436},[1086,6100,4441],{"class":1096},[1086,6102,6103,6105,6107,6110,6112],{"class":1088,"line":1715},[1086,6104,6046],{"class":1146},[1086,6106,6016],{"class":1436},[1086,6108,6109],{"class":1096},"## Domain hints",[1086,6111,6089],{"class":1436},[1086,6113,4441],{"class":1146},[1086,6115,6116,6118,6121,6123],{"class":1088,"line":3409},[1086,6117,6046],{"class":1146},[1086,6119,6120],{"class":1096},"- `final_income` is income converted to the target currency",[1086,6122,6016],{"class":1436},[1086,6124,4441],{"class":1146},[1086,6126,6127,6129,6132,6134],{"class":1088,"line":3415},[1086,6128,6046],{"class":1146},[1086,6130,6131],{"class":1096},"- `retailer_union` is the normalized retailer name",[1086,6133,6016],{"class":1436},[1086,6135,4441],{"class":1146},[1086,6137,6138,6140,6143,6145],{"class":1088,"line":3421},[1086,6139,6046],{"class":1146},[1086,6141,6142],{"class":1096},"- Always filter out empty strings when grouping by text fields",[1086,6144,6016],{"class":1436},[1086,6146,4441],{"class":1146},[1086,6148,6149,6151,6153,6156,6158],{"class":1088,"line":3427},[1086,6150,6046],{"class":1146},[1086,6152,6016],{"class":1436},[1086,6154,6155],{"class":1096},"## Output rules",[1086,6157,6089],{"class":1436},[1086,6159,4441],{"class":1146},[1086,6161,6162,6164,6167,6169],{"class":1088,"line":3433},[1086,6163,6046],{"class":1146},[1086,6165,6166],{"class":1096},"1. Output ONLY a single SELECT statement",[1086,6168,6016],{"class":1436},[1086,6170,4441],{"class":1146},[1086,6172,6173,6175,6178,6180],{"class":1088,"line":3439},[1086,6174,6046],{"class":1146},[1086,6176,6177],{"class":1096},"2. Always include a LIMIT clause (max 100)",[1086,6179,6016],{"class":1436},[1086,6181,4441],{"class":1146},[1086,6183,6184,6186,6189,6191],{"class":1088,"line":3444},[1086,6185,6046],{"class":1146},[1086,6187,6188],{"class":1096},"3. Never use INSERT, UPDATE, DELETE, DROP, or any DDL",[1086,6190,6016],{"class":1436},[1086,6192,4441],{"class":1146},[1086,6194,6195,6197,6199,6201,6204,6206,6208,6211,6213],{"class":1088,"line":3450},[1086,6196,6058],{"class":1155},[1086,6198,1159],{"class":1096},[1086,6200,6016],{"class":1436},[1086,6202,6203],{"class":1096},"## Examples",[1086,6205,6089],{"class":1436},[1086,6207,4409],{"class":1187},[1086,6209,6210],{"class":1436},"examples_block",[1086,6212,4423],{"class":1187},[1086,6214,4441],{"class":1096},[1086,6216,6217],{"class":1088,"line":3456},[1086,6218,6219],{"class":1146},"    )\n",[842,6221,6222],{},"The few-shot examples are critical. They teach the model patterns like subqueries for hierarchical groupings (\"top 5 artists with their retailers by income\") that a generic LLM would otherwise flatten into a single GROUP BY:",[1013,6224,6226],{"className":2441,"code":6225,"language":2443,"meta":728,"style":728},"-- Example: top 5 artists with their retailers by income\nSELECT artist, retailer_union, sum(final_income) AS total_income\nFROM analytics_streamdatach\nWHERE artist != '' AND retailer_union != ''\n  AND artist IN (\n    SELECT artist FROM analytics_streamdatach\n    WHERE artist != ''\n    GROUP BY artist ORDER BY sum(final_income) DESC LIMIT 5\n  )\nGROUP BY artist, retailer_union\nORDER BY artist, total_income DESC LIMIT 100\n",[895,6227,6228,6233,6251,6258,6282,6294,6305,6316,6339,6344,6351],{"__ignoreMap":728},[1086,6229,6230],{"class":1088,"line":1089},[1086,6231,6232],{"class":1427},"-- Example: top 5 artists with their retailers by income\n",[1086,6234,6235,6237,6240,6243,6246,6248],{"class":1088,"line":729},[1086,6236,2450],{"class":1187},[1086,6238,6239],{"class":1436}," artist, retailer_union, ",[1086,6241,6242],{"class":1105},"sum",[1086,6244,6245],{"class":1436},"(final_income) ",[1086,6247,2462],{"class":1187},[1086,6249,6250],{"class":1436}," total_income\n",[1086,6252,6253,6255],{"class":1088,"line":1112},[1086,6254,2470],{"class":1187},[1086,6256,6257],{"class":1436}," analytics_streamdatach\n",[1086,6259,6260,6262,6265,6268,6271,6274,6277,6279],{"class":1088,"line":1181},[1086,6261,2504],{"class":1187},[1086,6263,6264],{"class":1436}," artist ",[1086,6266,6267],{"class":1146},"!=",[1086,6269,6270],{"class":1146}," ''",[1086,6272,6273],{"class":1187}," AND",[1086,6275,6276],{"class":1436}," retailer_union ",[1086,6278,6267],{"class":1146},[1086,6280,6281],{"class":1146}," ''\n",[1086,6283,6284,6287,6289,6292],{"class":1088,"line":1205},[1086,6285,6286],{"class":1187},"  AND",[1086,6288,6264],{"class":1436},[1086,6290,6291],{"class":1187},"IN",[1086,6293,6041],{"class":1436},[1086,6295,6296,6299,6301,6303],{"class":1088,"line":1276},[1086,6297,6298],{"class":1187},"    SELECT",[1086,6300,6264],{"class":1436},[1086,6302,2470],{"class":1187},[1086,6304,6257],{"class":1436},[1086,6306,6307,6310,6312,6314],{"class":1088,"line":1282},[1086,6308,6309],{"class":1187},"    WHERE",[1086,6311,6264],{"class":1436},[1086,6313,6267],{"class":1146},[1086,6315,6281],{"class":1146},[1086,6317,6318,6321,6323,6325,6328,6330,6333,6336],{"class":1088,"line":1288},[1086,6319,6320],{"class":1187},"    GROUP BY",[1086,6322,6264],{"class":1436},[1086,6324,2540],{"class":1187},[1086,6326,6327],{"class":1105}," sum",[1086,6329,6245],{"class":1436},[1086,6331,6332],{"class":1187},"DESC",[1086,6334,6335],{"class":1187}," LIMIT",[1086,6337,6338],{"class":1187}," 5\n",[1086,6340,6341],{"class":1088,"line":2685},[1086,6342,6343],{"class":1436},"  )\n",[1086,6345,6346,6348],{"class":1088,"line":2700},[1086,6347,2532],{"class":1187},[1086,6349,6350],{"class":1436}," artist, retailer_union\n",[1086,6352,6353,6355,6358,6360,6362],{"class":1088,"line":3398},[1086,6354,2540],{"class":1187},[1086,6356,6357],{"class":1436}," artist, total_income ",[1086,6359,6332],{"class":1187},[1086,6361,6335],{"class":1187},[1086,6363,6364],{"class":1187}," 100\n",[842,6366,6367],{},"The result is that even someone who has never seen a database can ask:",[958,6369,6370,6373,6376],{},[961,6371,6372],{},"\"Monthly income trend for 2024\" and get a line chart",[961,6374,6375],{},"\"Income by country\" and get a ranked bar chart",[961,6377,6378],{},"\"Top 10 tracks by streams\" and get a sortable table with the data",[863,6380,6382],{"id":6381},"data-privacy-keeping-everything-local","Data privacy: keeping everything local",[842,6384,6385],{},"Here is where most \"AI analytics\" solutions stumble. They require sending your data, or at least your queries, to a third-party API. For a music company handling confidential royalty data, artist earnings, and pre-release catalogue information, that is often a non-starter.",[842,6387,6388],{},"Our architecture solves this with a pluggable LLM design:",[1045,6390,6392,6400],{"className":6391},[1048,1049,1765,1051,1052],[1054,6393,6397],{"description":6394,"icon":6395,"title":6396},"Open-source LLM runtime running in Docker alongside the application. All inference happens on your infrastructure. Zero data exposure.","i-lucide-server","Ollama (Local, Default)",[842,6398,6399],{},"Recommended for production environments handling sensitive royalty data. Runs models like Mistral 7B locally with 4GB of memory.",[1054,6401,6404],{"description":6402,"icon":2917,"title":6403},"Anthropic's Claude API for higher-quality SQL generation. Swap in with a single environment variable change.","Claude API (Optional)",[842,6405,6406],{},"Useful for development, testing, or environments where data sensitivity is lower and query accuracy is the priority.",[842,6408,6409],{},"Switching between providers is a configuration change, not a code change. The factory pattern reads a single setting and returns the right client:",[1013,6411,6414],{"className":1368,"code":6412,"filename":6413,"language":1250,"meta":728,"style":728},"def get_llm_provider() -> LLMProvider:\n    provider = getattr(settings, \"AI_DASHBOARD_PROVIDER\", \"ollama\")\n\n    if provider == \"claude\":\n        from .claude import ClaudeProvider\n        return ClaudeProvider()\n\n    from .ollama import OllamaProvider\n    return OllamaProvider()\n","factory.py",[895,6415,6416,6432,6466,6470,6490,6507,6516,6520,6535],{"__ignoreMap":728},[1086,6417,6418,6420,6423,6425,6427,6430],{"class":1088,"line":1089},[1086,6419,1392],{"class":1155},[1086,6421,6422],{"class":1105}," get_llm_provider",[1086,6424,2516],{"class":1146},[1086,6426,1413],{"class":1146},[1086,6428,6429],{"class":1436}," LLMProvider",[1086,6431,1418],{"class":1146},[1086,6433,6434,6437,6439,6442,6444,6446,6448,6450,6453,6455,6457,6459,6462,6464],{"class":1088,"line":729},[1086,6435,6436],{"class":1436},"    provider ",[1086,6438,1440],{"class":1146},[1086,6440,6441],{"class":1105}," getattr",[1086,6443,1398],{"class":1146},[1086,6445,4104],{"class":1105},[1086,6447,1227],{"class":1146},[1086,6449,1195],{"class":1146},[1086,6451,6452],{"class":1096},"AI_DASHBOARD_PROVIDER",[1086,6454,1159],{"class":1146},[1086,6456,1227],{"class":1146},[1086,6458,1195],{"class":1146},[1086,6460,6461],{"class":1096},"ollama",[1086,6463,1159],{"class":1146},[1086,6465,1455],{"class":1146},[1086,6467,6468],{"class":1088,"line":1112},[1086,6469,3390],{"emptyLinePlaceholder":738},[1086,6471,6472,6475,6478,6481,6483,6486,6488],{"class":1088,"line":1181},[1086,6473,6474],{"class":1423},"    if",[1086,6476,6477],{"class":1436}," provider ",[1086,6479,6480],{"class":1146},"==",[1086,6482,1195],{"class":1146},[1086,6484,6485],{"class":1096},"claude",[1086,6487,1159],{"class":1146},[1086,6489,1418],{"class":1146},[1086,6491,6492,6495,6498,6501,6504],{"class":1088,"line":1205},[1086,6493,6494],{"class":1423},"        from",[1086,6496,6497],{"class":1146}," .",[1086,6499,6500],{"class":1436},"claude ",[1086,6502,6503],{"class":1423},"import",[1086,6505,6506],{"class":1436}," ClaudeProvider\n",[1086,6508,6509,6511,6514],{"class":1088,"line":1276},[1086,6510,4239],{"class":1423},[1086,6512,6513],{"class":1105}," ClaudeProvider",[1086,6515,1387],{"class":1146},[1086,6517,6518],{"class":1088,"line":1282},[1086,6519,3390],{"emptyLinePlaceholder":738},[1086,6521,6522,6525,6527,6530,6532],{"class":1088,"line":1288},[1086,6523,6524],{"class":1423},"    from",[1086,6526,6497],{"class":1146},[1086,6528,6529],{"class":1436},"ollama ",[1086,6531,6503],{"class":1423},[1086,6533,6534],{"class":1436}," OllamaProvider\n",[1086,6536,6537,6539,6542],{"class":1088,"line":2685},[1086,6538,1460],{"class":1423},[1086,6540,6541],{"class":1105}," OllamaProvider",[1086,6543,1387],{"class":1146},[842,6545,6546],{},"The same application code, the same security layer, the same chart rendering.",[1032,6548,6549],{},[842,6550,6551],{},"The pluggable pattern means you can start with Ollama for privacy, evaluate quality, and switch to Claude API for specific use cases without any migration work.",[863,6553,6555],{"id":6554},"security-by-design-not-by-afterthought","Security by design, not by afterthought",[842,6557,6558],{},"Letting an AI write SQL that runs against your production database sounds risky. It is, if you do it naively. Our approach layers multiple security controls:",[842,6560,6561,6564],{},[996,6562,6563],{},"SQL Guard"," validates every query before execution. Here is the core of it:",[1013,6566,6569],{"className":1368,"code":6567,"filename":6568,"language":1250,"meta":728,"style":728},"BLOCKED_KEYWORDS = [\n    \"INSERT\", \"UPDATE\", \"DELETE\", \"DROP\", \"ALTER\",\n    \"TRUNCATE\", \"CREATE\", \"GRANT\", \"REVOKE\",\n    \"ATTACH\", \"DETACH\", \"RENAME\", \"OPTIMIZE\", \"KILL\",\n]\n\nclass SQLGuard:\n    @staticmethod\n    def validate(sql: str) -> str:\n        sql = sql.rstrip(\";\").strip()\n\n        if not sql.upper().startswith(\"SELECT\"):\n            raise SQLGuardError(\"Only SELECT queries are allowed\")\n        if \";\" in sql:\n            raise SQLGuardError(\"Multiple statements are not allowed\")\n\n        sql_upper = sql.upper()\n        for keyword in BLOCKED_KEYWORDS:\n            if re.search(rf\"\\b{keyword}\\b\", sql_upper):\n                raise SQLGuardError(f\"Forbidden keyword: {keyword}\")\n\n        # Only allow the analytics table\n        for table in re.findall(r\"(?:FROM|JOIN)\\s+(\\w+)\", sql_upper):\n            if table.lower() != ALLOWED_TABLE:\n                raise SQLGuardError(f\"Table '{table}' is not allowed\")\n\n        # Enforce LIMIT \u003C= 100\n        return _enforce_limit(sql, sql_upper)\n","guard.py",[895,6570,6571,6581,6628,6666,6713,6717,6721,6730,6738,6761,6792,6796,6827,6846,6863,6880,6884,6899,6914,6951,6975,6979,6984,7035,7057,7081,7085,7090],{"__ignoreMap":728},[1086,6572,6573,6576,6578],{"class":1088,"line":1089},[1086,6574,6575],{"class":1436},"BLOCKED_KEYWORDS ",[1086,6577,1440],{"class":1146},[1086,6579,6580],{"class":1146}," [\n",[1086,6582,6583,6585,6588,6590,6592,6594,6597,6599,6601,6603,6606,6608,6610,6612,6615,6617,6619,6621,6624,6626],{"class":1088,"line":729},[1086,6584,1169],{"class":1146},[1086,6586,6587],{"class":1096},"INSERT",[1086,6589,1159],{"class":1146},[1086,6591,1227],{"class":1146},[1086,6593,1195],{"class":1146},[1086,6595,6596],{"class":1096},"UPDATE",[1086,6598,1159],{"class":1146},[1086,6600,1227],{"class":1146},[1086,6602,1195],{"class":1146},[1086,6604,6605],{"class":1096},"DELETE",[1086,6607,1159],{"class":1146},[1086,6609,1227],{"class":1146},[1086,6611,1195],{"class":1146},[1086,6613,6614],{"class":1096},"DROP",[1086,6616,1159],{"class":1146},[1086,6618,1227],{"class":1146},[1086,6620,1195],{"class":1146},[1086,6622,6623],{"class":1096},"ALTER",[1086,6625,1159],{"class":1146},[1086,6627,1202],{"class":1146},[1086,6629,6630,6632,6635,6637,6639,6641,6644,6646,6648,6650,6653,6655,6657,6659,6662,6664],{"class":1088,"line":1112},[1086,6631,1169],{"class":1146},[1086,6633,6634],{"class":1096},"TRUNCATE",[1086,6636,1159],{"class":1146},[1086,6638,1227],{"class":1146},[1086,6640,1195],{"class":1146},[1086,6642,6643],{"class":1096},"CREATE",[1086,6645,1159],{"class":1146},[1086,6647,1227],{"class":1146},[1086,6649,1195],{"class":1146},[1086,6651,6652],{"class":1096},"GRANT",[1086,6654,1159],{"class":1146},[1086,6656,1227],{"class":1146},[1086,6658,1195],{"class":1146},[1086,6660,6661],{"class":1096},"REVOKE",[1086,6663,1159],{"class":1146},[1086,6665,1202],{"class":1146},[1086,6667,6668,6670,6673,6675,6677,6679,6682,6684,6686,6688,6691,6693,6695,6697,6700,6702,6704,6706,6709,6711],{"class":1088,"line":1181},[1086,6669,1169],{"class":1146},[1086,6671,6672],{"class":1096},"ATTACH",[1086,6674,1159],{"class":1146},[1086,6676,1227],{"class":1146},[1086,6678,1195],{"class":1146},[1086,6680,6681],{"class":1096},"DETACH",[1086,6683,1159],{"class":1146},[1086,6685,1227],{"class":1146},[1086,6687,1195],{"class":1146},[1086,6689,6690],{"class":1096},"RENAME",[1086,6692,1159],{"class":1146},[1086,6694,1227],{"class":1146},[1086,6696,1195],{"class":1146},[1086,6698,6699],{"class":1096},"OPTIMIZE",[1086,6701,1159],{"class":1146},[1086,6703,1227],{"class":1146},[1086,6705,1195],{"class":1146},[1086,6707,6708],{"class":1096},"KILL",[1086,6710,1159],{"class":1146},[1086,6712,1202],{"class":1146},[1086,6714,6715],{"class":1088,"line":1205},[1086,6716,1273],{"class":1146},[1086,6718,6719],{"class":1088,"line":1276},[1086,6720,3390],{"emptyLinePlaceholder":738},[1086,6722,6723,6725,6728],{"class":1088,"line":1282},[1086,6724,4036],{"class":1155},[1086,6726,6727],{"class":1092}," SQLGuard",[1086,6729,1418],{"class":1146},[1086,6731,6732,6735],{"class":1088,"line":1288},[1086,6733,6734],{"class":1146},"    @",[1086,6736,6737],{"class":1092},"staticmethod\n",[1086,6739,6740,6742,6745,6747,6749,6751,6753,6755,6757,6759],{"class":1088,"line":2685},[1086,6741,4065],{"class":1155},[1086,6743,6744],{"class":1105}," validate",[1086,6746,1398],{"class":1146},[1086,6748,2443],{"class":1401},[1086,6750,1133],{"class":1146},[1086,6752,1407],{"class":1092},[1086,6754,1410],{"class":1146},[1086,6756,1413],{"class":1146},[1086,6758,1407],{"class":1092},[1086,6760,1418],{"class":1146},[1086,6762,6763,6766,6768,6771,6773,6776,6778,6780,6782,6784,6787,6790],{"class":1088,"line":2700},[1086,6764,6765],{"class":1436},"        sql ",[1086,6767,1440],{"class":1146},[1086,6769,6770],{"class":1436}," sql",[1086,6772,861],{"class":1146},[1086,6774,6775],{"class":1105},"rstrip",[1086,6777,1398],{"class":1146},[1086,6779,1159],{"class":1146},[1086,6781,4639],{"class":1096},[1086,6783,1159],{"class":1146},[1086,6785,6786],{"class":1146},").",[1086,6788,6789],{"class":1105},"strip",[1086,6791,1387],{"class":1146},[1086,6793,6794],{"class":1088,"line":3398},[1086,6795,3390],{"emptyLinePlaceholder":738},[1086,6797,6798,6801,6804,6806,6808,6811,6814,6817,6819,6821,6823,6825],{"class":1088,"line":1715},[1086,6799,6800],{"class":1423},"        if",[1086,6802,6803],{"class":1146}," not",[1086,6805,6770],{"class":1436},[1086,6807,861],{"class":1146},[1086,6809,6810],{"class":1105},"upper",[1086,6812,6813],{"class":1146},"().",[1086,6815,6816],{"class":1105},"startswith",[1086,6818,1398],{"class":1146},[1086,6820,1159],{"class":1146},[1086,6822,2450],{"class":1096},[1086,6824,1159],{"class":1146},[1086,6826,4047],{"class":1146},[1086,6828,6829,6832,6835,6837,6839,6842,6844],{"class":1088,"line":3409},[1086,6830,6831],{"class":1423},"            raise",[1086,6833,6834],{"class":1105}," SQLGuardError",[1086,6836,1398],{"class":1146},[1086,6838,1159],{"class":1146},[1086,6840,6841],{"class":1096},"Only SELECT queries are allowed",[1086,6843,1159],{"class":1146},[1086,6845,1455],{"class":1146},[1086,6847,6848,6850,6852,6854,6856,6859,6861],{"class":1088,"line":3415},[1086,6849,6800],{"class":1423},[1086,6851,1195],{"class":1146},[1086,6853,4639],{"class":1096},[1086,6855,1159],{"class":1146},[1086,6857,6858],{"class":1146}," in",[1086,6860,6770],{"class":1436},[1086,6862,1418],{"class":1146},[1086,6864,6865,6867,6869,6871,6873,6876,6878],{"class":1088,"line":3421},[1086,6866,6831],{"class":1423},[1086,6868,6834],{"class":1105},[1086,6870,1398],{"class":1146},[1086,6872,1159],{"class":1146},[1086,6874,6875],{"class":1096},"Multiple statements are not allowed",[1086,6877,1159],{"class":1146},[1086,6879,1455],{"class":1146},[1086,6881,6882],{"class":1088,"line":3427},[1086,6883,3390],{"emptyLinePlaceholder":738},[1086,6885,6886,6889,6891,6893,6895,6897],{"class":1088,"line":3433},[1086,6887,6888],{"class":1436},"        sql_upper ",[1086,6890,1440],{"class":1146},[1086,6892,6770],{"class":1436},[1086,6894,861],{"class":1146},[1086,6896,6810],{"class":1105},[1086,6898,1387],{"class":1146},[1086,6900,6901,6904,6907,6909,6912],{"class":1088,"line":3439},[1086,6902,6903],{"class":1423},"        for",[1086,6905,6906],{"class":1436}," keyword ",[1086,6908,5931],{"class":1423},[1086,6910,6911],{"class":1436}," BLOCKED_KEYWORDS",[1086,6913,1418],{"class":1146},[1086,6915,6916,6919,6922,6924,6926,6928,6931,6934,6936,6939,6941,6944,6946,6949],{"class":1088,"line":3444},[1086,6917,6918],{"class":1423},"            if",[1086,6920,6921],{"class":1436}," re",[1086,6923,861],{"class":1146},[1086,6925,827],{"class":1105},[1086,6927,1398],{"class":1146},[1086,6929,6930],{"class":1155},"rf",[1086,6932,6933],{"class":1096},"\"\\b",[1086,6935,4409],{"class":1187},[1086,6937,6938],{"class":1105},"keyword",[1086,6940,4423],{"class":1187},[1086,6942,6943],{"class":1096},"\\b\"",[1086,6945,1227],{"class":1146},[1086,6947,6948],{"class":1105}," sql_upper",[1086,6950,4047],{"class":1146},[1086,6952,6953,6956,6958,6960,6962,6965,6967,6969,6971,6973],{"class":1088,"line":3450},[1086,6954,6955],{"class":1423},"                raise",[1086,6957,6834],{"class":1105},[1086,6959,1398],{"class":1146},[1086,6961,5962],{"class":1155},[1086,6963,6964],{"class":1096},"\"Forbidden keyword: ",[1086,6966,4409],{"class":1187},[1086,6968,6938],{"class":1105},[1086,6970,4423],{"class":1187},[1086,6972,1159],{"class":1096},[1086,6974,1455],{"class":1146},[1086,6976,6977],{"class":1088,"line":3456},[1086,6978,3390],{"emptyLinePlaceholder":738},[1086,6980,6981],{"class":1088,"line":3462},[1086,6982,6983],{"class":1427},"        # Only allow the analytics table\n",[1086,6985,6986,6988,6991,6993,6995,6997,7000,7002,7005,7008,7010,7013,7015,7017,7020,7023,7026,7029,7031,7033],{"class":1088,"line":3467},[1086,6987,6903],{"class":1423},[1086,6989,6990],{"class":1436}," table ",[1086,6992,5931],{"class":1423},[1086,6994,6921],{"class":1436},[1086,6996,861],{"class":1146},[1086,6998,6999],{"class":1105},"findall",[1086,7001,1398],{"class":1146},[1086,7003,7004],{"class":1155},"r",[1086,7006,7007],{"class":1146},"\"(?:",[1086,7009,2470],{"class":1096},[1086,7011,7012],{"class":1146},"|",[1086,7014,2478],{"class":1096},[1086,7016,1410],{"class":1146},[1086,7018,7019],{"class":1096},"\\s",[1086,7021,7022],{"class":1146},"+(",[1086,7024,7025],{"class":1096},"\\w",[1086,7027,7028],{"class":1146},"+)\"",[1086,7030,1227],{"class":1146},[1086,7032,6948],{"class":1105},[1086,7034,4047],{"class":1146},[1086,7036,7037,7039,7042,7044,7047,7049,7052,7055],{"class":1088,"line":3473},[1086,7038,6918],{"class":1423},[1086,7040,7041],{"class":1436}," table",[1086,7043,861],{"class":1146},[1086,7045,7046],{"class":1105},"lower",[1086,7048,2516],{"class":1146},[1086,7050,7051],{"class":1146}," !=",[1086,7053,7054],{"class":1436}," ALLOWED_TABLE",[1086,7056,1418],{"class":1146},[1086,7058,7059,7061,7063,7065,7067,7070,7072,7074,7076,7079],{"class":1088,"line":3479},[1086,7060,6955],{"class":1423},[1086,7062,6834],{"class":1105},[1086,7064,1398],{"class":1146},[1086,7066,5962],{"class":1155},[1086,7068,7069],{"class":1096},"\"Table '",[1086,7071,4409],{"class":1187},[1086,7073,871],{"class":1105},[1086,7075,4423],{"class":1187},[1086,7077,7078],{"class":1096},"' is not allowed\"",[1086,7080,1455],{"class":1146},[1086,7082,7083],{"class":1088,"line":3485},[1086,7084,3390],{"emptyLinePlaceholder":738},[1086,7086,7087],{"class":1088,"line":3491},[1086,7088,7089],{"class":1427},"        # Enforce LIMIT \u003C= 100\n",[1086,7091,7092,7094,7097,7099,7101,7103,7105],{"class":1088,"line":3497},[1086,7093,4239],{"class":1423},[1086,7095,7096],{"class":1105}," _enforce_limit",[1086,7098,1398],{"class":1146},[1086,7100,2443],{"class":1105},[1086,7102,1227],{"class":1146},[1086,7104,6948],{"class":1105},[1086,7106,1455],{"class":1146},[842,7108,7109],{},"Key constraints enforced:",[958,7111,7112,7127,7130,7136],{},[961,7113,7114,7115,7117,7118,5660,7120,5660,7122,5660,7124,7126],{},"Only ",[895,7116,2450],{}," statements are allowed. Any ",[895,7119,6587],{},[895,7121,6596],{},[895,7123,6605],{},[895,7125,6614],{},", or DDL keyword is blocked",[961,7128,7129],{},"Queries are restricted to a single analytics table (no access to user accounts, credentials, or other application data)",[961,7131,7132,7133,7135],{},"Result sets are capped at 100 rows, with ",[895,7134,2840],{}," enforced automatically",[961,7137,7138],{},"Comments, semicolons, and multi-statement queries are stripped or rejected",[842,7140,7141,7144],{},[996,7142,7143],{},"Read-only execution"," provides a second layer. The ClickHouse query runs over HTTP with safety parameters baked into every request:",[1013,7146,7149],{"className":1368,"code":7147,"filename":7148,"language":1250,"meta":728,"style":728},"response = httpx.post(\n    f\"http://{host}:{port}\",\n    params={\n        \"query\": f\"{sql} FORMAT JSON\",\n        \"database\": \"default\",\n        \"readonly\": \"1\",\n        \"max_execution_time\": \"10\",\n        \"max_result_rows\": \"100\",\n    },\n    timeout=15,\n)\n","executor.py",[895,7150,7151,7168,7196,7204,7230,7250,7269,7289,7309,7314,7326],{"__ignoreMap":728},[1086,7152,7153,7156,7158,7161,7163,7166],{"class":1088,"line":1089},[1086,7154,7155],{"class":1436},"response ",[1086,7157,1440],{"class":1146},[1086,7159,7160],{"class":1436}," httpx",[1086,7162,861],{"class":1146},[1086,7164,7165],{"class":1105},"post",[1086,7167,4094],{"class":1146},[1086,7169,7170,7173,7176,7178,7181,7183,7185,7187,7190,7192,7194],{"class":1088,"line":729},[1086,7171,7172],{"class":1155},"    f",[1086,7174,7175],{"class":1096},"\"http://",[1086,7177,4409],{"class":1187},[1086,7179,7180],{"class":1105},"host",[1086,7182,4423],{"class":1187},[1086,7184,1133],{"class":1096},[1086,7186,4409],{"class":1187},[1086,7188,7189],{"class":1105},"port",[1086,7191,4423],{"class":1187},[1086,7193,1159],{"class":1096},[1086,7195,1202],{"class":1146},[1086,7197,7198,7201],{"class":1088,"line":1112},[1086,7199,7200],{"class":1401},"    params",[1086,7202,7203],{"class":1146},"={\n",[1086,7205,7206,7208,7211,7213,7215,7217,7219,7221,7223,7225,7228],{"class":1088,"line":1181},[1086,7207,6046],{"class":1146},[1086,7209,7210],{"class":1096},"query",[1086,7212,1159],{"class":1146},[1086,7214,1133],{"class":1146},[1086,7216,4403],{"class":1155},[1086,7218,1159],{"class":1096},[1086,7220,4409],{"class":1187},[1086,7222,2443],{"class":1105},[1086,7224,4423],{"class":1187},[1086,7226,7227],{"class":1096}," FORMAT JSON\"",[1086,7229,1202],{"class":1146},[1086,7231,7232,7234,7237,7239,7241,7243,7246,7248],{"class":1088,"line":1205},[1086,7233,6046],{"class":1146},[1086,7235,7236],{"class":1096},"database",[1086,7238,1159],{"class":1146},[1086,7240,1133],{"class":1146},[1086,7242,1195],{"class":1146},[1086,7244,7245],{"class":1096},"default",[1086,7247,1159],{"class":1146},[1086,7249,1202],{"class":1146},[1086,7251,7252,7254,7257,7259,7261,7263,7265,7267],{"class":1088,"line":1276},[1086,7253,6046],{"class":1146},[1086,7255,7256],{"class":1096},"readonly",[1086,7258,1159],{"class":1146},[1086,7260,1133],{"class":1146},[1086,7262,1195],{"class":1146},[1086,7264,4434],{"class":1096},[1086,7266,1159],{"class":1146},[1086,7268,1202],{"class":1146},[1086,7270,7271,7273,7276,7278,7280,7282,7285,7287],{"class":1088,"line":1282},[1086,7272,6046],{"class":1146},[1086,7274,7275],{"class":1096},"max_execution_time",[1086,7277,1159],{"class":1146},[1086,7279,1133],{"class":1146},[1086,7281,1195],{"class":1146},[1086,7283,7284],{"class":1096},"10",[1086,7286,1159],{"class":1146},[1086,7288,1202],{"class":1146},[1086,7290,7291,7293,7296,7298,7300,7302,7305,7307],{"class":1088,"line":1288},[1086,7292,6046],{"class":1146},[1086,7294,7295],{"class":1096},"max_result_rows",[1086,7297,1159],{"class":1146},[1086,7299,1133],{"class":1146},[1086,7301,1195],{"class":1146},[1086,7303,7304],{"class":1096},"100",[1086,7306,1159],{"class":1146},[1086,7308,1202],{"class":1146},[1086,7310,7311],{"class":1088,"line":2685},[1086,7312,7313],{"class":1146},"    },\n",[1086,7315,7316,7319,7321,7324],{"class":1088,"line":2700},[1086,7317,7318],{"class":1401},"    timeout",[1086,7320,1440],{"class":1146},[1086,7322,7323],{"class":1187},"15",[1086,7325,1202],{"class":1146},[1086,7327,7328],{"class":1088,"line":3398},[1086,7329,1455],{"class":1146},[842,7331,7332,7333,7336],{},"Even if SQL Guard missed something, ClickHouse itself would block writes (",[895,7334,7335],{},"readonly=1","), kill slow queries (10 seconds), and cap the result set.",[842,7338,7339],{},"This belt-and-suspenders approach means that even if the LLM generates a malicious query (unlikely, but possible), it cannot modify data, access unauthorised tables, or run expensive long-running operations.",[1901,7341,7342],{},[842,7343,7344],{},"No AI system should have write access to production data. Always enforce read-only execution at both the application layer and the database layer.",[863,7346,7348],{"id":7347},"why-clickhouse-for-music-analytics","Why ClickHouse for music analytics",[842,7350,7351],{},"We chose ClickHouse as the analytics engine for MusicData Lab because music streaming data is a textbook columnar analytics workload: append-only, time-series, high-volume, and query-heavy.",[842,7353,7354],{},"A typical label might have 10 to 50 million rows of streaming data, partitioned by month. Common queries aggregate by artist, retailer, territory, or time period. ClickHouse handles these in milliseconds where PostgreSQL would take seconds or minutes.",[842,7356,7357],{},"Key advantages for music data:",[958,7359,7360,7366,7383,7389],{},[961,7361,7362,7365],{},[996,7363,7364],{},"MergeTree engine"," with monthly partitioning matches the natural cadence of royalty reporting",[961,7367,7368,7371,7372,5660,7375,7378,7379,7382],{},[996,7369,7370],{},"Low-cardinality string optimisation"," is perfect for fields like ",[895,7373,7374],{},"retailer_union",[895,7376,7377],{},"artist",", and ",[895,7380,7381],{},"country_code"," that have a bounded set of values",[961,7384,7385,7388],{},[996,7386,7387],{},"Columnar compression"," keeps storage costs low even as data grows to hundreds of millions of rows",[961,7390,7391,7394],{},[996,7392,7393],{},"HTTP API"," makes it straightforward to build read-only query interfaces with proper access controls",[863,7396,7398],{"id":7397},"what-we-learned-building-this","What we learned building this",[842,7400,7401],{},"Three insights from the implementation that apply to any company considering AI-powered analytics:",[1074,7403,7405],{"id":7404},"_1-the-prompt-is-the-product","1. The prompt is the product",[842,7407,7408],{},"The quality of SQL generation depends almost entirely on the system prompt. Including the full schema, domain-specific hints, and few-shot examples made the difference between \"sometimes works\" and \"reliably useful.\" As shown in the code above, we dynamically build the prompt from Django model metadata, so it stays in sync with schema changes automatically. No manual updates when a column is added or renamed.",[1074,7410,7412],{"id":7411},"_2-start-local-scale-to-cloud","2. Start local, scale to cloud",[842,7414,7415],{},"Running Ollama locally for development and testing removed the biggest adoption barrier: \"we can't send data to an external API.\" Once stakeholders see the value, the conversation about using a cloud API for better quality becomes much easier.",[1074,7417,7419],{"id":7418},"_3-security-is-a-feature-not-a-constraint","3. Security is a feature, not a constraint",[842,7421,7422],{},"The SQL Guard and read-only execution are not just safety nets. They are what made the business comfortable deploying this. When your CFO asks \"can this AI delete our data?\", the answer needs to be a confident \"no, here's why.\"",[863,7424,7426],{"id":7425},"who-is-this-for","Who is this for?",[842,7428,7429],{},"This is not a product launch. It is a proof of concept that we built for our own platform and for our clients. If you recognise any of these situations, it might be relevant to you:",[958,7431,7432,7435,7438,7441],{},[961,7433,7434],{},"Your data team spends too much time answering ad-hoc reporting requests",[961,7436,7437],{},"Business stakeholders wait days for insights that should take seconds",[961,7439,7440],{},"You have sensitive royalty or financial data and cannot use cloud-based AI tools",[961,7442,7443],{},"You already have analytics data in ClickHouse, PostgreSQL, or a similar database and want to make it more accessible",[842,7445,7446],{},"At MusicTech Lab, we build data platforms for the music industry. The AI Dashboard is one piece of a larger system that handles royalty ingestion, normalisation, currency conversion, and reporting. If this resonates, we should talk.",[863,7448,7450],{"id":7449},"what-comes-next","What comes next",[842,7452,7453],{},"This is a v1. The underlying pattern, natural language to SQL to visualisation, is not limited to music data. Any company with a structured analytics database can benefit from making that data conversational. The technology is ready. The question is whether your organisation is ready to let business users ask their own questions.",[1680,7455,7456],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .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 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 .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}",{"title":728,"searchDepth":729,"depth":729,"links":7458},[7459,7460,7461,7462,7463,7464,7465,7470,7471],{"id":5703,"depth":729,"text":5704},{"id":5738,"depth":729,"text":5739},{"id":5874,"depth":729,"text":5875},{"id":6381,"depth":729,"text":6382},{"id":6554,"depth":729,"text":6555},{"id":7347,"depth":729,"text":7348},{"id":7397,"depth":729,"text":7398,"children":7466},[7467,7468,7469],{"id":7404,"depth":1112,"text":7405},{"id":7411,"depth":1112,"text":7412},{"id":7418,"depth":1112,"text":7419},{"id":7425,"depth":729,"text":7426},{"id":7449,"depth":729,"text":7450},"2026-03-24T00:00:00.000Z","Music royalty data is complex. Non-technical stakeholders need insights without SQL. Here's how we built an AI dashboard that turns plain English into charts.",[7475,7478,7481,7484],{"question":7476,"answer":7477},"Can the AI dashboard work without sending data to the cloud?","Yes. The default setup uses Ollama, an open-source local LLM runtime. All queries, data, and model inference stay on your own infrastructure. No streaming data, royalty figures, or artist names ever leave your servers.",{"question":7479,"answer":7480},"What kind of questions can non-technical users ask?","Users can ask plain English questions like 'top 5 artists by income', 'monthly revenue trend for 2024', or 'income by country'. The AI generates a ClickHouse SQL query, executes it safely, and returns a chart with the results.",{"question":7482,"answer":7483},"How does the system prevent dangerous SQL queries?","A dedicated SQL security layer validates every generated query before execution. It enforces SELECT-only access, restricts queries to a single analytics table, caps result sizes, and runs against a read-only ClickHouse connection with query timeouts.",{"question":7485,"answer":7486},"What technology stack powers the AI dashboard?","Django for the web framework, ClickHouse for columnar analytics, Ollama (or Claude API) for LLM inference, and Chart.js for visualization. The architecture is pluggable, so the LLM provider can be swapped without changing application code.",{"src":7488},"/images/blog/musictechlab_blog_ai-powered-analytics-dashboard.webp",{"enabled":738,"items":7490},[7491,7493,7496,7499],{"text":7492,"icon":3844},"Business users type questions in plain English and get charts back in seconds, no SQL needed.",{"text":7494,"icon":7495},"Ollama runs locally by default so no royalty data ever leaves your servers.","i-lucide-shield",{"text":7497,"icon":7498},"SQL Guard enforces SELECT-only, single-table access with a 100-row limit on every query.","i-lucide-lock",{"text":7500,"icon":2939},"ClickHouse handles 10-50M rows with sub-second query times for columnar analytics.",{},{"title":7503,"description":7504},"AI Analytics Dashboard for Music Data | MusicTech Lab","How we built a generative AI dashboard that turns natural language into ClickHouse SQL and charts - with local LLM, zero data exposure.",[7506,5523,3189,7507,731,7508],"ai-analytics","llm","data-visualization","pS6Y0h5-QTtmmgq3FGyIwCVS8U7L5KQD1FBN31L-WS8",{"id":7511,"title":164,"authors":7512,"badge":7515,"body":7516,"category":731,"client":723,"date":8714,"description":8715,"extension":734,"faq":8716,"featured":69,"featuredOrder":723,"hidden":69,"image":8729,"keyTakeaways":8731,"meta":8742,"navigation":738,"path":165,"seo":8743,"status":723,"stem":166,"tags":8746,"teaser":723,"__hash__":8751},"posts/blog/music-data/mtl-bandcamp-mcp-open-source-revenue-dashboard.md",[7513],{"name":719,"to":720,"avatar":7514},{"src":722},{"label":837,"color":838},{"type":725,"value":7517,"toc":8688},[7518,7525,7530,7534,7540,7562,7569,7573,7576,7597,7605,7609,7613,7667,7671,7724,7728,7798,7802,7805,7809,7812,7818,7821,7834,7902,7906,7912,7915,8017,8021,8027,8147,8161,8165,8171,8174,8178,8181,8270,8275,8279,8282,8288,8347,8353,8420,8426,8471,8474,8478,8481,8485,8501,8504,8507,8511,8538,8542,8582,8585,8592,8610,8613,8626,8630,8633,8656,8665,8667,8670,8677,8682,8685],[842,7519,7520,7521,7524],{},"Every music label running on Bandcamp gets monthly CSV revenue reports. These files contain everything: gross revenue, net payouts, fee breakdowns, artist splits, and catalog details. But opening them in a spreadsheet means manual filtering, pivot tables, and context switching. We built ",[996,7522,7523],{},"mtl-bandcamp-mcp"," so you can query your Bandcamp earnings using natural language, without leaving the terminal.",[1572,7526,7527],{},[842,7528,7529],{},"All artist names, revenue figures, and catalog numbers in this article are fictional. We use fake data throughout to demonstrate the tool's capabilities.",[863,7531,7533],{"id":7532},"why-we-built-this","Why We Built This",[842,7535,7536,7537,7539],{},"At MusicTech Lab, we built ",[846,7538,3311],{"href":3310}," - a music distribution and analytics product. As part of that platform, we wrote an adapter that ingests Bandcamp revenue reports into our data pipeline. It is not something we work on daily - Bandcamp is one of many data sources - but when a new CSV export arrives, we need quick answers without opening a spreadsheet. An MCP server turned out to be the perfect fit: it lets us query reports on demand and could eventually help us integrate and debug the adapter within the broader data pipeline:",[1045,7541,7543,7548,7552,7557],{"className":7542},[1048,1049,1765,1051,1052],[1054,7544],{"description":7545,"icon":7546,"title":7547},"See totals, margins, and fee breakdowns at a glance.","i-lucide-layout-dashboard","Dashboard view",[1054,7549],{"description":7550,"icon":4845,"title":7551},"Revenue grouped by artist with share percentages.","Artist splits",[1054,7553],{"description":7554,"icon":7555,"title":7556},"Rank items by net revenue, gross revenue, or quantity sold.","i-lucide-trophy","Top sellers",[1054,7558],{"description":7559,"icon":7560,"title":7561},"Full detail for any catalog number including UPC, ISRC, and region.","i-lucide-search","Item lookup",[842,7563,7564,7565,7568],{},"Since we already use Claude Code for development, building an MCP server was the natural choice. We took a similar approach with our ",[846,7566,7567],{"href":85},"AI-powered analytics dashboard",", where business users query streaming royalty data in plain English and get interactive charts back.",[863,7570,7572],{"id":7571},"the-tools","The Tools",[842,7574,7575],{},"mtl-bandcamp-mcp exposes five tools:",[1045,7577,7579,7584,7588,7592],{"className":7578},[1048,1049,1765,1051,1052],[1054,7580],{"description":7581,"icon":7582,"title":7583},"List available Bandcamp CSV report files in the configured reports directory.","i-lucide-folder-open","bandcamp_list_reports",[1054,7585],{"description":7586,"icon":3844,"title":7587},"Dashboard summary with totals, net margin, and a full fee breakdown by category.","bandcamp_summary",[1054,7589],{"description":7590,"icon":4845,"title":7591},"Revenue grouped by artist showing quantity, gross, net, and share of total.","bandcamp_by_artist",[1054,7593],{"description":7594,"icon":7595,"title":7596},"Top-selling items sorted by net revenue, gross revenue, or quantity.","i-lucide-arrow-up-down","bandcamp_top_items",[1045,7598,7600],{"className":7599},[1048,1049,1765,1051,1052],[1054,7601],{"description":7602,"icon":7603,"title":7604},"Full detail for a specific catalog number: UPC, ISRC, region, all fees, and net revenue.","i-lucide-file-search","bandcamp_item_detail",[863,7606,7608],{"id":7607},"dashboard-fields","Dashboard Fields",[1074,7610,7612],{"id":7611},"summary-metrics","Summary metrics",[871,7614,7615,7625],{},[874,7616,7617],{},[877,7618,7619,7622],{},[880,7620,7621],{},"Metric",[880,7623,7624],{},"Description",[887,7626,7627,7635,7643,7651,7659],{},[877,7628,7629,7632],{},[892,7630,7631],{},"Items sold",[892,7633,7634],{},"Total quantity across all line items",[877,7636,7637,7640],{},[892,7638,7639],{},"Unique artists",[892,7641,7642],{},"Number of distinct artists in the report",[877,7644,7645,7648],{},[892,7646,7647],{},"Gross revenue",[892,7649,7650],{},"Total revenue before deductions",[877,7652,7653,7656],{},[892,7654,7655],{},"Net revenue",[892,7657,7658],{},"Revenue after all fees and taxes",[877,7660,7661,7664],{},[892,7662,7663],{},"Net margin",[892,7665,7666],{},"Net as a percentage of gross",[1074,7668,7670],{"id":7669},"fee-breakdown","Fee breakdown",[871,7672,7673,7682],{},[874,7674,7675],{},[877,7676,7677,7680],{},[880,7678,7679],{},"Fee type",[880,7681,7624],{},[887,7683,7684,7692,7700,7708,7716],{},[877,7685,7686,7689],{},[892,7687,7688],{},"Taxes",[892,7690,7691],{},"VAT and other taxes collected",[877,7693,7694,7697],{},[892,7695,7696],{},"Bandcamp share",[892,7698,7699],{},"Bandcamp's assessed revenue share",[877,7701,7702,7705],{},[892,7703,7704],{},"Collection society",[892,7706,7707],{},"Royalty collection society share",[877,7709,7710,7713],{},[892,7711,7712],{},"Payment processing",[892,7714,7715],{},"Payment processor fees",[877,7717,7718,7721],{},[892,7719,7720],{},"Shipping",[892,7722,7723],{},"Physical goods shipping costs",[1074,7725,7727],{"id":7726},"item-detail","Item detail",[871,7729,7730,7742],{},[874,7731,7732],{},[877,7733,7734,7737,7739],{},[880,7735,7736],{},"Field",[880,7738,7624],{},[880,7740,7741],{},"Example",[887,7743,7744,7755,7766,7776,7787],{},[877,7745,7746,7749,7752],{},[892,7747,7748],{},"Cat no.",[892,7750,7751],{},"Catalog number",[892,7753,7754],{},"\"MDL-2939\"",[877,7756,7757,7760,7763],{},[892,7758,7759],{},"UPC",[892,7761,7762],{},"Universal Product Code",[892,7764,7765],{},"\"5060000000001\"",[877,7767,7768,7770,7773],{},[892,7769,3856],{},[892,7771,7772],{},"International Standard Recording Code",[892,7774,7775],{},"\"GBAP01900028\"",[877,7777,7778,7781,7784],{},[892,7779,7780],{},"Item type",[892,7782,7783],{},"Track, album, or merch",[892,7785,7786],{},"\"track\"",[877,7788,7789,7792,7795],{},[892,7790,7791],{},"Region",[892,7793,7794],{},"Buyer's region",[892,7796,7797],{},"\"United Kingdom\"",[863,7799,7801],{"id":7800},"how-it-works-in-practice","How It Works in Practice",[842,7803,7804],{},"Once installed, you just talk to Claude Code. No spreadsheet formulas, no pivot tables, no context switching.",[1074,7806,7808],{"id":7807},"viewing-the-dashboard","Viewing the dashboard",[842,7810,7811],{},"Ask Claude for a summary:",[1013,7813,7816],{"className":7814,"code":7815,"language":1018,"meta":728},[1016],"> Show me a summary of my latest Bandcamp revenue report\n",[895,7817,7815],{"__ignoreMap":728},[842,7819,7820],{},"Claude finds the most recent CSV and returns a formatted dashboard:",[842,7822,7823,7826,7827,7830,7833],{},[996,7824,7825],{},"Report:"," ",[895,7828,7829],{},"bandcamp-rev-report-2026-q1.csv",[996,7831,7832],{},"Period:"," 2026-01-01 to 2026-03-31",[871,7835,7836,7845],{},[874,7837,7838],{},[877,7839,7840,7842],{},[880,7841,7621],{},[880,7843,7844],{},"Value",[887,7846,7847,7854,7862,7869,7876,7884,7895],{},[877,7848,7849,7851],{},[892,7850,7631],{},[892,7852,7853],{},"847",[877,7855,7856,7859],{},[892,7857,7858],{},"Line items",[892,7860,7861],{},"312",[877,7863,7864,7866],{},[892,7865,7639],{},[892,7867,7868],{},"24",[877,7870,7871,7873],{},[892,7872,7647],{},[892,7874,7875],{},"12,450.00 GBP",[877,7877,7878,7881],{},[892,7879,7880],{},"Total deductions",[892,7882,7883],{},"3,290.75 GBP",[877,7885,7886,7890],{},[892,7887,7888],{},[996,7889,7655],{},[892,7891,7892],{},[996,7893,7894],{},"9,159.25 GBP",[877,7896,7897,7899],{},[892,7898,7663],{},[892,7900,7901],{},"73.6%",[1074,7903,7905],{"id":7904},"checking-artist-splits","Checking artist splits",[1013,7907,7910],{"className":7908,"code":7909,"language":1018,"meta":728},[1016],"> Which artists earned the most this quarter?\n",[895,7911,7909],{"__ignoreMap":728},[842,7913,7914],{},"Claude groups revenue by artist and shows each one's share:",[871,7916,7917,7939],{},[874,7918,7919],{},[877,7920,7921,7924,7927,7930,7933,7936],{},[880,7922,7923],{},"Artist",[880,7925,7926],{},"Items",[880,7928,7929],{},"Qty Sold",[880,7931,7932],{},"Gross",[880,7934,7935],{},"Net",[880,7937,7938],{},"Share",[887,7940,7941,7961,7980,7999],{},[877,7942,7943,7946,7949,7952,7955,7958],{},[892,7944,7945],{},"Solar Cole",[892,7947,7948],{},"3",[892,7950,7951],{},"18",[892,7953,7954],{},"167.27 GBP",[892,7956,7957],{},"129.38 GBP",[892,7959,7960],{},"53.9%",[877,7962,7963,7966,7968,7971,7974,7977],{},[892,7964,7965],{},"The Fox Alliance",[892,7967,4434],{},[892,7969,7970],{},"6",[892,7972,7973],{},"64.55 GBP",[892,7975,7976],{},"53.11 GBP",[892,7978,7979],{},"22.1%",[877,7981,7982,7985,7987,7990,7993,7996],{},[892,7983,7984],{},"Midnight Storm",[892,7986,4434],{},[892,7988,7989],{},"4",[892,7991,7992],{},"33.57 GBP",[892,7994,7995],{},"28.81 GBP",[892,7997,7998],{},"12.0%",[877,8000,8001,8004,8006,8008,8011,8014],{},[892,8002,8003],{},"Iris Grove",[892,8005,1483],{},[892,8007,1483],{},[892,8009,8010],{},"16.80 GBP",[892,8012,8013],{},"11.72 GBP",[892,8015,8016],{},"4.9%",[1074,8018,8020],{"id":8019},"finding-top-sellers","Finding top sellers",[1013,8022,8025],{"className":8023,"code":8024,"language":1018,"meta":728},[1016],"> What are the top 5 items by quantity sold?\n",[895,8026,8024],{"__ignoreMap":728},[871,8028,8029,8051],{},[874,8030,8031],{},[877,8032,8033,8036,8039,8041,8044,8047,8049],{},[880,8034,8035],{},"#",[880,8037,8038],{},"Item",[880,8040,7923],{},[880,8042,8043],{},"Type",[880,8045,8046],{},"Qty",[880,8048,7932],{},[880,8050,7935],{},[887,8052,8053,8074,8091,8110,8127],{},[877,8054,8055,8057,8060,8062,8065,8068,8071],{},[892,8056,4434],{},[892,8058,8059],{},"The Little Fantasies",[892,8061,7945],{},[892,8063,8064],{},"album",[892,8066,8067],{},"9",[892,8069,8070],{},"83.87 GBP",[892,8072,8073],{},"64.31 GBP",[877,8075,8076,8078,8081,8083,8085,8087,8089],{},[892,8077,1483],{},[892,8079,8080],{},"Short Dreams",[892,8082,7965],{},[892,8084,8064],{},[892,8086,7970],{},[892,8088,7973],{},[892,8090,7976],{},[877,8092,8093,8095,8098,8100,8102,8104,8107],{},[892,8094,7948],{},[892,8096,8097],{},"The Open Offerings",[892,8099,7945],{},[892,8101,8064],{},[892,8103,7970],{},[892,8105,8106],{},"55.02 GBP",[892,8108,8109],{},"44.01 GBP",[877,8111,8112,8114,8117,8119,8121,8123,8125],{},[892,8113,7989],{},[892,8115,8116],{},"The Broad Chorus",[892,8118,7984],{},[892,8120,8064],{},[892,8122,7989],{},[892,8124,7992],{},[892,8126,7995],{},[877,8128,8129,8132,8135,8137,8139,8141,8144],{},[892,8130,8131],{},"5",[892,8133,8134],{},"Premier Visions",[892,8136,7945],{},[892,8138,8064],{},[892,8140,7948],{},[892,8142,8143],{},"28.38 GBP",[892,8145,8146],{},"21.06 GBP",[842,8148,8149,8150,5660,8153,8156,8157,8160],{},"You can sort by ",[895,8151,8152],{},"net",[895,8154,8155],{},"gross",", or ",[895,8158,8159],{},"quantity"," and set any limit.",[1074,8162,8164],{"id":8163},"looking-up-a-specific-item","Looking up a specific item",[1013,8166,8169],{"className":8167,"code":8168,"language":1018,"meta":728},[1016],"> Show me all the details for catalog number MDL-2939\n",[895,8170,8168],{"__ignoreMap":728},[842,8172,8173],{},"Claude returns every field for that item: UPC, ISRC, region, item type, and a full fee breakdown.",[1074,8175,8177],{"id":8176},"more-prompt-ideas","More prompt ideas",[842,8179,8180],{},"Here are some real-world prompts you can try. Click each one to see the result:",[1045,8182,8185,8210,8225,8240,8255],{"className":8183},[8184,1052],"space-y-2",[8186,8187,8195,8199],"details",{"className":8188},[8189,8190,8191,8192,8193,8194],"rounded-lg","border","border-gray-700","px-4","py-3","cursor-pointer",[8196,8197,8198],"summary",{},"List all available Bandcamp reports",[1045,8200,8204],{"className":8201},[8202,8189,8190,8191,8203],"mt-4","overflow-hidden",[842,8205,8206],{},[1027,8207],{"alt":8208,"src":8209},"Listing available Bandcamp reports in Claude Code","/images/blog/musictechlab_blog_mtl-bandcamp-mcp-list-reports.webp",[8186,8211,8213,8216],{"className":8212},[8189,8190,8191,8192,8193,8194],[8196,8214,8215],{},"What are the top 5 items by quantity sold?",[1045,8217,8219],{"className":8218},[8202,8189,8190,8191,8203],[842,8220,8221],{},[1027,8222],{"alt":8223,"src":8224},"Top 5 items by quantity sold in Claude Code","/images/blog/musictechlab_blog_mtl-bandcamp-mcp-top-items.webp",[8186,8226,8228,8231],{"className":8227},[8189,8190,8191,8192,8193,8194],[8196,8229,8230],{},"How much did we pay in Bandcamp fees this quarter?",[1045,8232,8234],{"className":8233},[8202,8189,8190,8191,8203],[842,8235,8236],{},[1027,8237],{"alt":8238,"src":8239},"Bandcamp fee breakdown in Claude Code","/images/blog/musictechlab_blog_mtl-bandcamp-mcp-fees.webp",[8186,8241,8243,8246],{"className":8242},[8189,8190,8191,8192,8193,8194],[8196,8244,8245],{},"Which artist has the best net margin?",[1045,8247,8249],{"className":8248},[8202,8189,8190,8191,8203],[842,8250,8251],{},[1027,8252],{"alt":8253,"src":8254},"Artist net margin analysis in Claude Code","/images/blog/musictechlab_blog_mtl-bandcamp-mcp-margin.webp",[8186,8256,8258,8261],{"className":8257},[8189,8190,8191,8192,8193,8194],[8196,8259,8260],{},"Show me the fee breakdown — what percentage goes to taxes vs Bandcamp vs payment processing?",[1045,8262,8264],{"className":8263},[8202,8189,8190,8191,8203],[842,8265,8266],{},[1027,8267],{"alt":8268,"src":8269},"Fee breakdown by percentage in Claude Code","/images/blog/musictechlab_blog_mtl-bandcamp-mcp-fee-breakdown.webp",[1032,8271,8272],{},[842,8273,8274],{},"You can chain prompts naturally. Ask Claude for a summary first, then drill into a specific artist or item. The conversation context carries over, so Claude remembers the report it just loaded.",[1074,8276,8278],{"id":8277},"visualizing-the-data","Visualizing the data",[842,8280,8281],{},"You can also generate charts from the report data. Just ask:",[1013,8283,8286],{"className":8284,"code":8285,"language":1018,"meta":728},[1016],"> Show me which artists earned the most as a chart\n",[895,8287,8285],{"__ignoreMap":728},[1013,8289,8291],{"className":3341,"code":8290,"language":3343,"meta":728,"style":728},"---\nconfig:\n    xyChart:\n        width: 800\n        height: 500\n---\nxychart-beta\n    title \"Net Revenue by Artist (GBP)\"\n    x-axis [\"Solar Cole\", \"Fox Alliance\", \"Midnight Storm\", \"Iris Grove\", \"Shaw Protocol\", \"Orion Webb\", \"Zane Element\"]\n    y-axis \"Net Revenue (GBP)\" 0 --> 140\n    bar [129.38, 53.11, 28.81, 11.72, 9.42, 6.82, 0.78]\n",[895,8292,8293,8298,8303,8308,8313,8318,8322,8327,8332,8337,8342],{"__ignoreMap":728},[1086,8294,8295],{"class":1088,"line":1089},[1086,8296,8297],{"class":1436},"---\n",[1086,8299,8300],{"class":1088,"line":729},[1086,8301,8302],{"class":1436},"config:\n",[1086,8304,8305],{"class":1088,"line":1112},[1086,8306,8307],{"class":1436},"    xyChart:\n",[1086,8309,8310],{"class":1088,"line":1181},[1086,8311,8312],{"class":1436},"        width: 800\n",[1086,8314,8315],{"class":1088,"line":1205},[1086,8316,8317],{"class":1436},"        height: 500\n",[1086,8319,8320],{"class":1088,"line":1276},[1086,8321,8297],{"class":1436},[1086,8323,8324],{"class":1088,"line":1282},[1086,8325,8326],{"class":1436},"xychart-beta\n",[1086,8328,8329],{"class":1088,"line":1288},[1086,8330,8331],{"class":1436},"    title \"Net Revenue by Artist (GBP)\"\n",[1086,8333,8334],{"class":1088,"line":2685},[1086,8335,8336],{"class":1436},"    x-axis [\"Solar Cole\", \"Fox Alliance\", \"Midnight Storm\", \"Iris Grove\", \"Shaw Protocol\", \"Orion Webb\", \"Zane Element\"]\n",[1086,8338,8339],{"class":1088,"line":2700},[1086,8340,8341],{"class":1436},"    y-axis \"Net Revenue (GBP)\" 0 --> 140\n",[1086,8343,8344],{"class":1088,"line":3398},[1086,8345,8346],{"class":1436},"    bar [129.38, 53.11, 28.81, 11.72, 9.42, 6.82, 0.78]\n",[1013,8348,8351],{"className":8349,"code":8350,"language":1018,"meta":728},[1016],"> Show me the revenue share breakdown as a pie chart\n",[895,8352,8350],{"__ignoreMap":728},[1013,8354,8356],{"className":3341,"code":8355,"language":3343,"meta":728,"style":728},"---\nconfig:\n    pie:\n        useWidth: 700\n---\npie title Revenue Share by Artist\n    \"Solar Cole\" : 53.9\n    \"The Fox Alliance\" : 22.1\n    \"Midnight Storm\" : 12.0\n    \"Iris Grove\" : 4.9\n    \"The Shaw Protocol\" : 3.9\n    \"Orion Webb\" : 2.8\n    \"The Zane Element\" : 0.3\n",[895,8357,8358,8362,8366,8371,8376,8380,8385,8390,8395,8400,8405,8410,8415],{"__ignoreMap":728},[1086,8359,8360],{"class":1088,"line":1089},[1086,8361,8297],{"class":1436},[1086,8363,8364],{"class":1088,"line":729},[1086,8365,8302],{"class":1436},[1086,8367,8368],{"class":1088,"line":1112},[1086,8369,8370],{"class":1436},"    pie:\n",[1086,8372,8373],{"class":1088,"line":1181},[1086,8374,8375],{"class":1436},"        useWidth: 700\n",[1086,8377,8378],{"class":1088,"line":1205},[1086,8379,8297],{"class":1436},[1086,8381,8382],{"class":1088,"line":1276},[1086,8383,8384],{"class":1436},"pie title Revenue Share by Artist\n",[1086,8386,8387],{"class":1088,"line":1282},[1086,8388,8389],{"class":1436},"    \"Solar Cole\" : 53.9\n",[1086,8391,8392],{"class":1088,"line":1288},[1086,8393,8394],{"class":1436},"    \"The Fox Alliance\" : 22.1\n",[1086,8396,8397],{"class":1088,"line":2685},[1086,8398,8399],{"class":1436},"    \"Midnight Storm\" : 12.0\n",[1086,8401,8402],{"class":1088,"line":2700},[1086,8403,8404],{"class":1436},"    \"Iris Grove\" : 4.9\n",[1086,8406,8407],{"class":1088,"line":3398},[1086,8408,8409],{"class":1436},"    \"The Shaw Protocol\" : 3.9\n",[1086,8411,8412],{"class":1088,"line":1715},[1086,8413,8414],{"class":1436},"    \"Orion Webb\" : 2.8\n",[1086,8416,8417],{"class":1088,"line":3409},[1086,8418,8419],{"class":1436},"    \"The Zane Element\" : 0.3\n",[1013,8421,8424],{"className":8422,"code":8423,"language":1018,"meta":728},[1016],"> What percentage of fees goes to taxes vs Bandcamp vs payment processing?\n",[895,8425,8423],{"__ignoreMap":728},[1013,8427,8429],{"className":3341,"code":8428,"language":3343,"meta":728,"style":728},"---\nconfig:\n    pie:\n        useWidth: 700\n---\npie title Fee Breakdown (% of Total Fees)\n    \"Taxes\" : 46.6\n    \"Bandcamp Share\" : 31.2\n    \"Payment Processing\" : 22.1\n",[895,8430,8431,8435,8439,8443,8447,8451,8456,8461,8466],{"__ignoreMap":728},[1086,8432,8433],{"class":1088,"line":1089},[1086,8434,8297],{"class":1436},[1086,8436,8437],{"class":1088,"line":729},[1086,8438,8302],{"class":1436},[1086,8440,8441],{"class":1088,"line":1112},[1086,8442,8370],{"class":1436},[1086,8444,8445],{"class":1088,"line":1181},[1086,8446,8375],{"class":1436},[1086,8448,8449],{"class":1088,"line":1205},[1086,8450,8297],{"class":1436},[1086,8452,8453],{"class":1088,"line":1276},[1086,8454,8455],{"class":1436},"pie title Fee Breakdown (% of Total Fees)\n",[1086,8457,8458],{"class":1088,"line":1282},[1086,8459,8460],{"class":1436},"    \"Taxes\" : 46.6\n",[1086,8462,8463],{"class":1088,"line":1288},[1086,8464,8465],{"class":1436},"    \"Bandcamp Share\" : 31.2\n",[1086,8467,8468],{"class":1088,"line":2685},[1086,8469,8470],{"class":1436},"    \"Payment Processing\" : 22.1\n",[842,8472,8473],{},"All charts are rendered inline, with the site's dark theme and brand styling. No spreadsheet, no BI tool, no context switching.",[863,8475,8477],{"id":8476},"handling-bandcamps-csv-format","Handling Bandcamp's CSV Format",[842,8479,8480],{},"Bandcamp exports revenue reports in UTF-16 encoding by default, which trips up most CSV parsers. mtl-bandcamp-mcp handles this transparently - it tries UTF-16 first, then falls back to UTF-8-sig and UTF-8. You never have to think about encoding issues.",[863,8482,8484],{"id":8483},"tech-stack","Tech Stack",[1045,8486,8488,8492,8497],{"className":8487},[1048,1049,1050,1051,1052],[1054,8489],{"description":8490,"icon":5365,"title":8491},"With Poetry for dependency management.","Python 3.10+",[1054,8493],{"description":8494,"icon":8495,"title":8496},"Standard library CSV parsing with smart encoding detection.","i-lucide-table","csv + io",[1054,8498],{"description":8499,"icon":8500,"title":1340},"Model Context Protocol server framework.","i-lucide-cpu",[842,8502,8503],{},"The entire codebase is a single server module with clean, focused logic.",[863,8505,8506],{"id":3083},"Getting Started",[1074,8508,8510],{"id":8509},"installation","Installation",[1013,8512,8514],{"className":1080,"code":8513,"language":1082,"meta":728,"style":728},"git clone https://github.com/musictechlab/mtl-bandcamp-mcp.git\ncd mtl-bandcamp-mcp\npoetry install\n",[895,8515,8516,8525,8532],{"__ignoreMap":728},[1086,8517,8518,8520,8522],{"class":1088,"line":1089},[1086,8519,1093],{"class":1092},[1086,8521,1097],{"class":1096},[1086,8523,8524],{"class":1096}," https://github.com/musictechlab/mtl-bandcamp-mcp.git\n",[1086,8526,8527,8529],{"class":1088,"line":729},[1086,8528,1106],{"class":1105},[1086,8530,8531],{"class":1096}," mtl-bandcamp-mcp\n",[1086,8533,8534,8536],{"class":1088,"line":1112},[1086,8535,1115],{"class":1092},[1086,8537,1118],{"class":1096},[1074,8539,8541],{"id":8540},"register-with-claude-code","Register with Claude Code",[1013,8543,8545],{"className":1080,"code":8544,"language":1082,"meta":728,"style":728},"claude mcp add -s user mtl-bandcamp -- bash -c \"cd /path/to/mtl-bandcamp-mcp && poetry run python -m mtl_bandcamp_mcp\"\n",[895,8546,8547],{"__ignoreMap":728},[1086,8548,8549,8551,8554,8557,8560,8563,8566,8569,8572,8575,8577,8580],{"class":1088,"line":1089},[1086,8550,6485],{"class":1092},[1086,8552,8553],{"class":1096}," mcp",[1086,8555,8556],{"class":1096}," add",[1086,8558,8559],{"class":1096}," -s",[1086,8561,8562],{"class":1096}," user",[1086,8564,8565],{"class":1096}," mtl-bandcamp",[1086,8567,8568],{"class":1096}," --",[1086,8570,8571],{"class":1096}," bash",[1086,8573,8574],{"class":1096}," -c",[1086,8576,1195],{"class":1146},[1086,8578,8579],{"class":1096},"cd /path/to/mtl-bandcamp-mcp && poetry run python -m mtl_bandcamp_mcp",[1086,8581,4441],{"class":1146},[1074,8583,2337],{"id":8584},"configuration",[842,8586,8587,8588,8591],{},"Set ",[895,8589,8590],{},"BANDCAMP_REPORTS_DIR"," to point to the directory containing your Bandcamp CSV exports:",[1013,8593,8595],{"className":1080,"code":8594,"language":1082,"meta":728,"style":728},"export BANDCAMP_REPORTS_DIR=~/Documents/bandcamp-reports\n",[895,8596,8597],{"__ignoreMap":728},[1086,8598,8599,8601,8604,8607],{"class":1088,"line":1089},[1086,8600,3625],{"class":1155},[1086,8602,8603],{"class":1436}," BANDCAMP_REPORTS_DIR",[1086,8605,8606],{"class":1146},"=~",[1086,8608,8609],{"class":1436},"/Documents/bandcamp-reports\n",[842,8611,8612],{},"Restart Claude Code, and all five tools are available immediately.",[1032,8614,8615],{},[842,8616,8617,8618,8621,8622,8625],{},"You can verify the server is connected by running ",[895,8619,8620],{},"claude mcp list"," - look for ",[895,8623,8624],{},"mtl-bandcamp"," in the output.",[863,8627,8629],{"id":8628},"what-is-next","What is Next",[842,8631,8632],{},"This is version 0.1.0. Here is what we are considering for future releases:",[1045,8634,8636,8641,8646,8651],{"className":8635},[1048,1049,1765,1051,1052],[1054,8637],{"description":8638,"icon":8639,"title":8640},"Compare two reports side by side to spot revenue trends and growth.","i-lucide-git-compare","Period Comparison",[1054,8642],{"description":8643,"icon":8644,"title":8645},"Revenue grouped by buyer region to understand geographic demand.","i-lucide-map","Regional Breakdown",[1054,8647],{"description":8648,"icon":8649,"title":8650},"Track revenue over multiple reporting periods with growth indicators.","i-lucide-line-chart","Trend Analysis",[1054,8652],{"description":8653,"icon":8654,"title":8655},"Push dashboard data directly to Google Sheets for sharing with stakeholders.","i-lucide-file-output","Export to Sheets",[842,8657,8658,8659,8664],{},"Have an idea for a feature we have not thought of? We would love to hear it. ",[846,8660,8663],{"href":8661,"rel":8662},"https://musictechlab.io/contact",[850],"Let's connect"," and shape the roadmap together.",[863,8666,837],{"id":1626},[842,8668,8669],{},"mtl-bandcamp-mcp is MIT licensed and available on GitHub:",[842,8671,8672],{},[846,8673,8676],{"href":8674,"rel":8675},"https://github.com/musictechlab/mtl-bandcamp-mcp",[850],"github.com/musictechlab/mtl-bandcamp-mcp",[1572,8678,8679],{},[842,8680,8681],{},"This project is experimental and in early development. Revenue data is read-only - the server never modifies your CSV files.",[842,8683,8684],{},"Contributions, issues, and feature requests are welcome. If you run a label on Bandcamp and want to streamline your revenue reporting, give it a try and let us know what you think.",[1680,8686,8687],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":728,"searchDepth":729,"depth":729,"links":8689},[8690,8691,8692,8697,8705,8706,8707,8712,8713],{"id":7532,"depth":729,"text":7533},{"id":7571,"depth":729,"text":7572},{"id":7607,"depth":729,"text":7608,"children":8693},[8694,8695,8696],{"id":7611,"depth":1112,"text":7612},{"id":7669,"depth":1112,"text":7670},{"id":7726,"depth":1112,"text":7727},{"id":7800,"depth":729,"text":7801,"children":8698},[8699,8700,8701,8702,8703,8704],{"id":7807,"depth":1112,"text":7808},{"id":7904,"depth":1112,"text":7905},{"id":8019,"depth":1112,"text":8020},{"id":8163,"depth":1112,"text":8164},{"id":8176,"depth":1112,"text":8177},{"id":8277,"depth":1112,"text":8278},{"id":8476,"depth":729,"text":8477},{"id":8483,"depth":729,"text":8484},{"id":3083,"depth":729,"text":8506,"children":8708},[8709,8710,8711],{"id":8509,"depth":1112,"text":8510},{"id":8540,"depth":1112,"text":8541},{"id":8584,"depth":1112,"text":2337},{"id":8628,"depth":729,"text":8629},{"id":1626,"depth":729,"text":837},"2026-03-20T00:00:00.000Z","An open source MCP server that turns Bandcamp CSV exports into queryable dashboards. Ask about artist splits, fee breakdowns, and top sellers — no spreadsheet needed.",[8717,8720,8723,8726],{"question":8718,"answer":8719},"What is mtl-bandcamp-mcp?","It is an open source MCP (Model Context Protocol) server that lets Claude Code parse Bandcamp revenue CSV exports and present interactive dashboards with summaries, artist breakdowns, and top-selling items.",{"question":8721,"answer":8722},"What file formats are supported?","Bandcamp CSV exports in both UTF-16 (Bandcamp default) and UTF-8 encodings.",{"question":8724,"answer":8725},"How do I install mtl-bandcamp-mcp?","Clone the repo, run poetry install, and register it with Claude Code using the claude mcp add command.",{"question":8727,"answer":8728},"What data can I see in the dashboard?","Gross and net revenue, fee breakdowns (taxes, Bandcamp share, collection society, payment processing, shipping), revenue by artist with share percentages, top-selling items, and detailed catalog item views.",{"src":8730},"/images/blog/musictechlab_blog_mtl-bandcamp-mcp.webp",{"enabled":738,"items":8732},[8733,8735,8738,8740],{"text":8734,"icon":3844},"Query Bandcamp revenue CSVs with natural language, no spreadsheets or pivot tables needed.",{"text":8736,"icon":8737},"Five tools cover dashboard summaries, artist splits, top sellers, and catalog item lookups.","i-lucide-blocks",{"text":8739,"icon":3850},"Handles Bandcamp's UTF-16 encoding transparently with automatic fallback to UTF-8.",{"text":8741,"icon":1723},"MIT licensed and open source, installable with Poetry and Claude Code in under 5 minutes.",{},{"title":8744,"description":8745},"mtl-bandcamp-mcp: Bandcamp Revenue Dashboard MCP Server | MusicTech Lab","Open source MCP server for analyzing Bandcamp revenue CSV reports. Dashboard summaries, artist breakdowns, and top sellers inside Claude Code.",[8747,8748,1626,8749,764,8750,3191],"MCP","Bandcamp","Claude Code","revenue","NsosocH7kjl1Sr0ml9fAVbkUSd8V4s07sXDgk675THk",{"id":8753,"title":168,"authors":8754,"badge":8757,"body":8758,"category":731,"client":723,"date":9704,"description":9705,"extension":734,"faq":9706,"featured":69,"featuredOrder":723,"hidden":69,"image":9718,"keyTakeaways":9720,"meta":9730,"navigation":738,"path":169,"seo":9731,"status":723,"stem":170,"tags":9734,"teaser":723,"__hash__":9736},"posts/blog/music-data/mtl-metadata-mcp-open-source-audio-metadata-embedding.md",[8755],{"name":719,"to":720,"avatar":8756},{"src":722},{"label":837,"color":838},{"type":725,"value":8759,"toc":9686},[8760,8767,8779,8781,8784,8807,8810,8812,8815,8837,8841,8917,8919,8922,8926,8929,8935,8938,9128,9131,9137,9141,9144,9150,9153,9159,9163,9166,9172,9470,9476,9479,9485,9487,9490,9496,9502,9508,9514,9520,9526,9532,9537,9539,9551,9554,9556,9558,9585,9587,9620,9623,9632,9634,9636,9658,9663,9665,9668,9675,9680,9683],[842,8761,8762,8763,8766],{},"Every music file carries invisible data: the artist name, album, genre, release year, and the ISRC code that ties a recording to its rights holder. When this metadata is missing or incorrect, royalties get lost, catalog searches fail, and distributors reject deliveries. We built ",[996,8764,8765],{},"mtl-metadata-mcp"," to solve this problem directly from the command line, using natural language.",[1572,8768,8769],{},[842,8770,8771,8774,8775,8778],{},[996,8772,8773],{},"What is MCP?"," The ",[846,8776,1677],{"href":1675,"rel":8777},[850]," (MCP) is an open standard that lets AI assistants like Claude connect to external tools. Instead of copying and pasting data between apps, you describe what you want in plain English and the AI calls the right tool automatically. MCP servers are lightweight programs that expose specific capabilities - reading files, querying APIs, or in our case, manipulating audio metadata.",[863,8780,7533],{"id":7532},[842,8782,8783],{},"At MusicTech Lab, we sometimes need to quickly verify or fix metadata in audio files - checking ISRC codes, correcting artist names, or spotting missing tags before a delivery. It is not our daily job, but when it comes up, we wanted a tool that lets us do it fast without leaving the terminal:",[1045,8785,8787,8792,8797,8802],{"className":8786},[1048,1049,1765,1051,1052],[1054,8788],{"description":8789,"icon":8790,"title":8791},"Read metadata from any audio file and present it clearly.","i-lucide-book-open","Read metadata",[1054,8793],{"description":8794,"icon":8795,"title":8796},"Write metadata using natural language commands.","i-lucide-pen-line","Write metadata",[1054,8798],{"description":8799,"icon":8800,"title":8801},"Scan entire directories to find files with missing or incomplete tags.","i-lucide-scan-search","Scan directories",[1054,8803],{"description":8804,"icon":8805,"title":8806},"Work inside our development workflow without switching contexts.","i-lucide-workflow","Stay in flow",[842,8808,8809],{},"Since we already use Claude Code for development, building an MCP server was the natural choice.",[863,8811,7572],{"id":7571},[842,8813,8814],{},"mtl-metadata-mcp exposes four tools:",[1045,8816,8818,8822,8827,8832],{"className":8817},[1048,1049,1765,1051,1052],[1054,8819],{"description":8820,"icon":7603,"title":8821},"Read current metadata tags and file info (format, duration, bitrate, sample rate, channels) from an audio file.","metadata_read",[1054,8823],{"description":8824,"icon":8825,"title":8826},"Embed or update title, artist, album, date, genre, and ISRC. Only the fields you specify are changed.","i-lucide-file-pen","metadata_write",[1054,8828],{"description":8829,"icon":8830,"title":8831},"Strip all metadata tags from a file. Audio data is preserved.","i-lucide-eraser","metadata_clear",[1054,8833],{"description":8834,"icon":8835,"title":8836},"Scan a directory for audio files and report which metadata fields are present or missing.","i-lucide-folder-search","metadata_scan",[863,8838,8840],{"id":8839},"supported-fields","Supported Fields",[871,8842,8843,8853],{},[874,8844,8845],{},[877,8846,8847,8849,8851],{},[880,8848,7736],{},[880,8850,7624],{},[880,8852,7741],{},[887,8854,8855,8866,8876,8886,8897,8908],{},[877,8856,8857,8860,8863],{},[892,8858,8859],{},"Title",[892,8861,8862],{},"Track name",[892,8864,8865],{},"\"Back in Black\"",[877,8867,8868,8870,8873],{},[892,8869,7923],{},[892,8871,8872],{},"Performer",[892,8874,8875],{},"\"AC/DC\"",[877,8877,8878,8881,8884],{},[892,8879,8880],{},"Album",[892,8882,8883],{},"Album name",[892,8885,8865],{},[877,8887,8888,8891,8894],{},[892,8889,8890],{},"Date",[892,8892,8893],{},"Release year",[892,8895,8896],{},"\"1980\"",[877,8898,8899,8902,8905],{},[892,8900,8901],{},"Genre",[892,8903,8904],{},"Music genre",[892,8906,8907],{},"\"Classic Rock\"",[877,8909,8910,8912,8914],{},[892,8911,3856],{},[892,8913,7772],{},[892,8915,8916],{},"\"AUAP07900028\"",[863,8918,7801],{"id":7800},[842,8920,8921],{},"Once installed, you just talk to Claude Code. No commands to memorize, no flags to look up.",[1074,8923,8925],{"id":8924},"reading-metadata","Reading metadata",[842,8927,8928],{},"Ask Claude to read a file:",[1013,8930,8933],{"className":8931,"code":8932,"language":1018,"meta":728},[1016],"> What metadata does back-in-black.mp3 have?\n",[895,8934,8932],{"__ignoreMap":728},[842,8936,8937],{},"The response comes back as structured data:",[1013,8939,8941],{"className":1136,"code":8940,"language":1139,"meta":728,"style":728},"{\n  \"file\": \"/Users/you/Music/back-in-black.mp3\",\n  \"format\": \"mp3\",\n  \"duration_seconds\": 254.38,\n  \"bitrate_kbps\": 128,\n  \"sample_rate_hz\": 44100,\n  \"channels\": 2,\n  \"metadata\": {\n    \"title\": \"Back in Black\",\n    \"artist\": \"ACDC\",\n    \"genre\": \"Classic Rock\"\n  }\n}\n",[895,8942,8943,8947,8967,8987,9003,9019,9035,9051,9063,9083,9102,9120,9124],{"__ignoreMap":728},[1086,8944,8945],{"class":1088,"line":1089},[1086,8946,1147],{"class":1146},[1086,8948,8949,8951,8954,8956,8958,8960,8963,8965],{"class":1088,"line":729},[1086,8950,1152],{"class":1146},[1086,8952,8953],{"class":1155},"file",[1086,8955,1159],{"class":1146},[1086,8957,1133],{"class":1146},[1086,8959,1195],{"class":1146},[1086,8961,8962],{"class":1096},"/Users/you/Music/back-in-black.mp3",[1086,8964,1159],{"class":1146},[1086,8966,1202],{"class":1146},[1086,8968,8969,8971,8974,8976,8978,8980,8983,8985],{"class":1088,"line":1112},[1086,8970,1152],{"class":1146},[1086,8972,8973],{"class":1155},"format",[1086,8975,1159],{"class":1146},[1086,8977,1133],{"class":1146},[1086,8979,1195],{"class":1146},[1086,8981,8982],{"class":1096},"mp3",[1086,8984,1159],{"class":1146},[1086,8986,1202],{"class":1146},[1086,8988,8989,8991,8994,8996,8998,9001],{"class":1088,"line":1181},[1086,8990,1152],{"class":1146},[1086,8992,8993],{"class":1155},"duration_seconds",[1086,8995,1159],{"class":1146},[1086,8997,1133],{"class":1146},[1086,8999,9000],{"class":1187}," 254.38",[1086,9002,1202],{"class":1146},[1086,9004,9005,9007,9010,9012,9014,9017],{"class":1088,"line":1205},[1086,9006,1152],{"class":1146},[1086,9008,9009],{"class":1155},"bitrate_kbps",[1086,9011,1159],{"class":1146},[1086,9013,1133],{"class":1146},[1086,9015,9016],{"class":1187}," 128",[1086,9018,1202],{"class":1146},[1086,9020,9021,9023,9026,9028,9030,9033],{"class":1088,"line":1276},[1086,9022,1152],{"class":1146},[1086,9024,9025],{"class":1155},"sample_rate_hz",[1086,9027,1159],{"class":1146},[1086,9029,1133],{"class":1146},[1086,9031,9032],{"class":1187}," 44100",[1086,9034,1202],{"class":1146},[1086,9036,9037,9039,9042,9044,9046,9049],{"class":1088,"line":1282},[1086,9038,1152],{"class":1146},[1086,9040,9041],{"class":1155},"channels",[1086,9043,1159],{"class":1146},[1086,9045,1133],{"class":1146},[1086,9047,9048],{"class":1187}," 2",[1086,9050,1202],{"class":1146},[1086,9052,9053,9055,9057,9059,9061],{"class":1088,"line":1288},[1086,9054,1152],{"class":1146},[1086,9056,3857],{"class":1155},[1086,9058,1159],{"class":1146},[1086,9060,1133],{"class":1146},[1086,9062,1164],{"class":1146},[1086,9064,9065,9067,9070,9072,9074,9076,9079,9081],{"class":1088,"line":2685},[1086,9066,1169],{"class":1146},[1086,9068,9069],{"class":1092},"title",[1086,9071,1159],{"class":1146},[1086,9073,1133],{"class":1146},[1086,9075,1195],{"class":1146},[1086,9077,9078],{"class":1096},"Back in Black",[1086,9080,1159],{"class":1146},[1086,9082,1202],{"class":1146},[1086,9084,9085,9087,9089,9091,9093,9095,9098,9100],{"class":1088,"line":2700},[1086,9086,1169],{"class":1146},[1086,9088,7377],{"class":1092},[1086,9090,1159],{"class":1146},[1086,9092,1133],{"class":1146},[1086,9094,1195],{"class":1146},[1086,9096,9097],{"class":1096},"ACDC",[1086,9099,1159],{"class":1146},[1086,9101,1202],{"class":1146},[1086,9103,9104,9106,9109,9111,9113,9115,9118],{"class":1088,"line":3398},[1086,9105,1169],{"class":1146},[1086,9107,9108],{"class":1092},"genre",[1086,9110,1159],{"class":1146},[1086,9112,1133],{"class":1146},[1086,9114,1195],{"class":1146},[1086,9116,9117],{"class":1096},"Classic Rock",[1086,9119,4441],{"class":1146},[1086,9121,9122],{"class":1088,"line":1715},[1086,9123,1285],{"class":1146},[1086,9125,9126],{"class":1088,"line":3409},[1086,9127,1291],{"class":1146},[842,9129,9130],{},"Claude then formats this into a readable table and points out missing fields - in this case, album, date, and ISRC.",[842,9132,9133],{},[1027,9134],{"alt":9135,"src":9136},"Reading metadata from back-in-black.mp3 in Claude Code","/images/blog/musictechlab_blog_mtl-metadata-mcp-read.webp",[1074,9138,9140],{"id":9139},"writing-metadata","Writing metadata",[842,9142,9143],{},"Tell Claude what to fill in:",[1013,9145,9148],{"className":9146,"code":9147,"language":1018,"meta":728},[1016],"> Set the album to \"Back in Black\", date to 1980, and ISRC to AUAP07900028\n",[895,9149,9147],{"__ignoreMap":728},[842,9151,9152],{},"Only the specified fields are updated. Everything else stays untouched.",[842,9154,9155],{},[1027,9156],{"alt":9157,"src":9158},"Writing metadata in Claude Code","/images/blog/musictechlab_blog_mtl-metadata-mcp-write.webp",[1074,9160,9162],{"id":9161},"scanning-a-library","Scanning a library",[842,9164,9165],{},"This is where it gets powerful:",[1013,9167,9170],{"className":9168,"code":9169,"language":1018,"meta":728},[1016],"> Scan ~/Music/demos and tell me which tracks are missing ISRC codes\n",[895,9171,9169],{"__ignoreMap":728},[1013,9173,9175],{"className":1136,"code":9174,"language":1139,"meta":728,"style":728},"{\n  \"directory\": \"/Users/you/Music/demos\",\n  \"total_files\": 3,\n  \"files\": [\n    {\n      \"file\": \"idea-01.mp3\",\n      \"has_metadata\": true,\n      \"fields_present\": [\"title\", \"artist\"],\n      \"fields_missing\": [\"album\", \"date\", \"genre\", \"isrc\"]\n    },\n    {\n      \"file\": \"sketch.flac\",\n      \"has_metadata\": false,\n      \"fields_present\": [],\n      \"fields_missing\": [\"title\", \"artist\", \"album\", \"date\", \"genre\", \"isrc\"]\n    }\n  ]\n}\n",[895,9176,9177,9181,9201,9217,9230,9235,9254,9268,9298,9344,9348,9352,9371,9384,9397,9457,9461,9466],{"__ignoreMap":728},[1086,9178,9179],{"class":1088,"line":1089},[1086,9180,1147],{"class":1146},[1086,9182,9183,9185,9188,9190,9192,9194,9197,9199],{"class":1088,"line":729},[1086,9184,1152],{"class":1146},[1086,9186,9187],{"class":1155},"directory",[1086,9189,1159],{"class":1146},[1086,9191,1133],{"class":1146},[1086,9193,1195],{"class":1146},[1086,9195,9196],{"class":1096},"/Users/you/Music/demos",[1086,9198,1159],{"class":1146},[1086,9200,1202],{"class":1146},[1086,9202,9203,9205,9208,9210,9212,9215],{"class":1088,"line":1112},[1086,9204,1152],{"class":1146},[1086,9206,9207],{"class":1155},"total_files",[1086,9209,1159],{"class":1146},[1086,9211,1133],{"class":1146},[1086,9213,9214],{"class":1187}," 3",[1086,9216,1202],{"class":1146},[1086,9218,9219,9221,9224,9226,9228],{"class":1088,"line":1181},[1086,9220,1152],{"class":1146},[1086,9222,9223],{"class":1155},"files",[1086,9225,1159],{"class":1146},[1086,9227,1133],{"class":1146},[1086,9229,6580],{"class":1146},[1086,9231,9232],{"class":1088,"line":1205},[1086,9233,9234],{"class":1146},"    {\n",[1086,9236,9237,9239,9241,9243,9245,9247,9250,9252],{"class":1088,"line":1276},[1086,9238,1184],{"class":1146},[1086,9240,8953],{"class":1092},[1086,9242,1159],{"class":1146},[1086,9244,1133],{"class":1146},[1086,9246,1195],{"class":1146},[1086,9248,9249],{"class":1096},"idea-01.mp3",[1086,9251,1159],{"class":1146},[1086,9253,1202],{"class":1146},[1086,9255,9256,9258,9261,9263,9265],{"class":1088,"line":1282},[1086,9257,1184],{"class":1146},[1086,9259,9260],{"class":1092},"has_metadata",[1086,9262,1159],{"class":1146},[1086,9264,1133],{"class":1146},[1086,9266,9267],{"class":1146}," true,\n",[1086,9269,9270,9272,9275,9277,9279,9281,9283,9285,9287,9289,9291,9293,9295],{"class":1088,"line":1288},[1086,9271,1184],{"class":1146},[1086,9273,9274],{"class":1092},"fields_present",[1086,9276,1159],{"class":1146},[1086,9278,1133],{"class":1146},[1086,9280,1217],{"class":1146},[1086,9282,1159],{"class":1146},[1086,9284,9069],{"class":1096},[1086,9286,1159],{"class":1146},[1086,9288,1227],{"class":1146},[1086,9290,1195],{"class":1146},[1086,9292,7377],{"class":1096},[1086,9294,1159],{"class":1146},[1086,9296,9297],{"class":1146},"],\n",[1086,9299,9300,9302,9305,9307,9309,9311,9313,9315,9317,9319,9321,9324,9326,9328,9330,9332,9334,9336,9338,9340,9342],{"class":1088,"line":2685},[1086,9301,1184],{"class":1146},[1086,9303,9304],{"class":1092},"fields_missing",[1086,9306,1159],{"class":1146},[1086,9308,1133],{"class":1146},[1086,9310,1217],{"class":1146},[1086,9312,1159],{"class":1146},[1086,9314,8064],{"class":1096},[1086,9316,1159],{"class":1146},[1086,9318,1227],{"class":1146},[1086,9320,1195],{"class":1146},[1086,9322,9323],{"class":1096},"date",[1086,9325,1159],{"class":1146},[1086,9327,1227],{"class":1146},[1086,9329,1195],{"class":1146},[1086,9331,9108],{"class":1096},[1086,9333,1159],{"class":1146},[1086,9335,1227],{"class":1146},[1086,9337,1195],{"class":1146},[1086,9339,1402],{"class":1096},[1086,9341,1159],{"class":1146},[1086,9343,1273],{"class":1146},[1086,9345,9346],{"class":1088,"line":2700},[1086,9347,7313],{"class":1146},[1086,9349,9350],{"class":1088,"line":3398},[1086,9351,9234],{"class":1146},[1086,9353,9354,9356,9358,9360,9362,9364,9367,9369],{"class":1088,"line":1715},[1086,9355,1184],{"class":1146},[1086,9357,8953],{"class":1092},[1086,9359,1159],{"class":1146},[1086,9361,1133],{"class":1146},[1086,9363,1195],{"class":1146},[1086,9365,9366],{"class":1096},"sketch.flac",[1086,9368,1159],{"class":1146},[1086,9370,1202],{"class":1146},[1086,9372,9373,9375,9377,9379,9381],{"class":1088,"line":3409},[1086,9374,1184],{"class":1146},[1086,9376,9260],{"class":1092},[1086,9378,1159],{"class":1146},[1086,9380,1133],{"class":1146},[1086,9382,9383],{"class":1146}," false,\n",[1086,9385,9386,9388,9390,9392,9394],{"class":1088,"line":3415},[1086,9387,1184],{"class":1146},[1086,9389,9274],{"class":1092},[1086,9391,1159],{"class":1146},[1086,9393,1133],{"class":1146},[1086,9395,9396],{"class":1146}," [],\n",[1086,9398,9399,9401,9403,9405,9407,9409,9411,9413,9415,9417,9419,9421,9423,9425,9427,9429,9431,9433,9435,9437,9439,9441,9443,9445,9447,9449,9451,9453,9455],{"class":1088,"line":3421},[1086,9400,1184],{"class":1146},[1086,9402,9304],{"class":1092},[1086,9404,1159],{"class":1146},[1086,9406,1133],{"class":1146},[1086,9408,1217],{"class":1146},[1086,9410,1159],{"class":1146},[1086,9412,9069],{"class":1096},[1086,9414,1159],{"class":1146},[1086,9416,1227],{"class":1146},[1086,9418,1195],{"class":1146},[1086,9420,7377],{"class":1096},[1086,9422,1159],{"class":1146},[1086,9424,1227],{"class":1146},[1086,9426,1195],{"class":1146},[1086,9428,8064],{"class":1096},[1086,9430,1159],{"class":1146},[1086,9432,1227],{"class":1146},[1086,9434,1195],{"class":1146},[1086,9436,9323],{"class":1096},[1086,9438,1159],{"class":1146},[1086,9440,1227],{"class":1146},[1086,9442,1195],{"class":1146},[1086,9444,9108],{"class":1096},[1086,9446,1159],{"class":1146},[1086,9448,1227],{"class":1146},[1086,9450,1195],{"class":1146},[1086,9452,1402],{"class":1096},[1086,9454,1159],{"class":1146},[1086,9456,1273],{"class":1146},[1086,9458,9459],{"class":1088,"line":3427},[1086,9460,1279],{"class":1146},[1086,9462,9463],{"class":1088,"line":3433},[1086,9464,9465],{"class":1146},"  ]\n",[1086,9467,9468],{"class":1088,"line":3439},[1086,9469,1291],{"class":1146},[842,9471,9472],{},[1027,9473],{"alt":9474,"src":9475},"Scanning a music library in Claude Code","/images/blog/musictechlab_blog_mtl-metadata-mcp-scan.webp",[842,9477,9478],{},"You can then follow up with batch commands:",[1013,9480,9483],{"className":9481,"code":9482,"language":1018,"meta":728},[1016],"> Set ISRC codes USAB12300001 through USAB12300003 on each track in order\n",[895,9484,9482],{"__ignoreMap":728},[1074,9486,8177],{"id":8176},[842,9488,9489],{},"Here are some real-world prompts you can try:",[1013,9491,9494],{"className":9492,"code":9493,"language":1018,"meta":728},[1016],"> Read the metadata from all files in ~/Music/masters and show me a summary table\n",[895,9495,9493],{"__ignoreMap":728},[1013,9497,9500],{"className":9498,"code":9499,"language":1018,"meta":728},[1016],"> Change the artist on demo-v3.mp3 from \"Unknown\" to \"Jane Doe\"\n",[895,9501,9499],{"__ignoreMap":728},[1013,9503,9506],{"className":9504,"code":9505,"language":1018,"meta":728},[1016],"> Which files in ~/Music/releases have no genre tag?\n",[895,9507,9505],{"__ignoreMap":728},[1013,9509,9512],{"className":9510,"code":9511,"language":1018,"meta":728},[1016],"> Copy the metadata from track-01.mp3 and apply it to track-01-remastered.mp3\n",[895,9513,9511],{"__ignoreMap":728},[1013,9515,9518],{"className":9516,"code":9517,"language":1018,"meta":728},[1016],"> Strip all metadata from every file in ~/Music/stems\n",[895,9519,9517],{"__ignoreMap":728},[1013,9521,9524],{"className":9522,"code":9523,"language":1018,"meta":728},[1016],"> Check if any FLAC files in ~/Music/archive are missing ISRC codes, then list them\n",[895,9525,9523],{"__ignoreMap":728},[1013,9527,9530],{"className":9528,"code":9529,"language":1018,"meta":728},[1016],"> Set genre to \"Electronic\" on all MP3 files in ~/Music/ep that currently have no genre\n",[895,9531,9529],{"__ignoreMap":728},[1032,9533,9534],{},[842,9535,9536],{},"You can chain prompts naturally. Ask Claude to scan first, review the results, then tell it what to fix. The conversation context carries over, so Claude remembers which files it just scanned.",[863,9538,8484],{"id":8483},[1045,9540,9542,9544,9549],{"className":9541},[1048,1049,1050,1051,1052],[1054,9543],{"description":8490,"icon":5365,"title":8491},[1054,9545],{"description":9546,"icon":9547,"title":9548},"Cross-format audio tag handling (ID3v2, Vorbis comments).","i-lucide-music","mutagen",[1054,9550],{"description":8499,"icon":8500,"title":1340},[842,9552,9553],{},"The entire codebase is under 200 lines of core logic, plus 14 automated tests.",[863,9555,8506],{"id":3083},[1074,9557,8510],{"id":8509},[1013,9559,9561],{"className":1080,"code":9560,"language":1082,"meta":728,"style":728},"git clone https://github.com/musictechlab/mtl-metadata-mcp.git\ncd mtl-metadata-mcp\npoetry install\n",[895,9562,9563,9572,9579],{"__ignoreMap":728},[1086,9564,9565,9567,9569],{"class":1088,"line":1089},[1086,9566,1093],{"class":1092},[1086,9568,1097],{"class":1096},[1086,9570,9571],{"class":1096}," https://github.com/musictechlab/mtl-metadata-mcp.git\n",[1086,9573,9574,9576],{"class":1088,"line":729},[1086,9575,1106],{"class":1105},[1086,9577,9578],{"class":1096}," mtl-metadata-mcp\n",[1086,9580,9581,9583],{"class":1088,"line":1112},[1086,9582,1115],{"class":1092},[1086,9584,1118],{"class":1096},[1074,9586,8541],{"id":8540},[1013,9588,9590],{"className":1080,"code":9589,"language":1082,"meta":728,"style":728},"claude mcp add -s user mtl-metadata -- bash -c \"cd /path/to/mtl-metadata-mcp && poetry run python -m mtl_metadata_mcp\"\n",[895,9591,9592],{"__ignoreMap":728},[1086,9593,9594,9596,9598,9600,9602,9604,9607,9609,9611,9613,9615,9618],{"class":1088,"line":1089},[1086,9595,6485],{"class":1092},[1086,9597,8553],{"class":1096},[1086,9599,8556],{"class":1096},[1086,9601,8559],{"class":1096},[1086,9603,8562],{"class":1096},[1086,9605,9606],{"class":1096}," mtl-metadata",[1086,9608,8568],{"class":1096},[1086,9610,8571],{"class":1096},[1086,9612,8574],{"class":1096},[1086,9614,1195],{"class":1146},[1086,9616,9617],{"class":1096},"cd /path/to/mtl-metadata-mcp && poetry run python -m mtl_metadata_mcp",[1086,9619,4441],{"class":1146},[842,9621,9622],{},"Restart Claude Code, and the four metadata tools are available immediately.",[1032,9624,9625],{},[842,9626,8617,9627,8621,9629,8625],{},[895,9628,8620],{},[895,9630,9631],{},"mtl-metadata",[863,9633,8629],{"id":8628},[842,9635,8632],{},[1045,9637,9639,9644,9649,9653],{"className":9638},[1048,1049,1765,1051,1052],[1054,9640],{"description":9641,"icon":9642,"title":9643},"Broadcast Wave Format (BWF) metadata for production and mastering workflows.","i-lucide-file-audio","WAV Support",[1054,9645],{"description":9646,"icon":9647,"title":9648},"Embed and extract album artwork directly from audio files.","i-lucide-image","Cover Art",[1054,9650],{"description":9651,"icon":1769,"title":9652},"A dedicated tool for applying metadata changes across entire folders in one go.","Batch Operations",[1054,9654],{"description":9655,"icon":9656,"title":9657},"Map metadata fields to DDEX ERN standards for catalog delivery workflows.","i-lucide-arrow-right-left","DDEX Field Mapping",[842,9659,8658,9660,8664],{},[846,9661,8663],{"href":8661,"rel":9662},[850],[863,9664,837],{"id":1626},[842,9666,9667],{},"mtl-metadata-mcp is MIT licensed and available on GitHub:",[842,9669,9670],{},[846,9671,9674],{"href":9672,"rel":9673},"https://github.com/musictechlab/mtl-metadata-mcp",[850],"github.com/musictechlab/mtl-metadata-mcp",[1572,9676,9677],{},[842,9678,9679],{},"This project is experimental and in early development. Always back up your audio files before modifying metadata.",[842,9681,9682],{},"Contributions, issues, and feature requests are welcome. If you work with music catalogs and want to streamline your metadata workflow, give it a try and let us know what you think.",[1680,9684,9685],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}",{"title":728,"searchDepth":729,"depth":729,"links":9687},[9688,9689,9690,9691,9697,9698,9702,9703],{"id":7532,"depth":729,"text":7533},{"id":7571,"depth":729,"text":7572},{"id":8839,"depth":729,"text":8840},{"id":7800,"depth":729,"text":7801,"children":9692},[9693,9694,9695,9696],{"id":8924,"depth":1112,"text":8925},{"id":9139,"depth":1112,"text":9140},{"id":9161,"depth":1112,"text":9162},{"id":8176,"depth":1112,"text":8177},{"id":8483,"depth":729,"text":8484},{"id":3083,"depth":729,"text":8506,"children":9699},[9700,9701],{"id":8509,"depth":1112,"text":8510},{"id":8540,"depth":1112,"text":8541},{"id":8628,"depth":729,"text":8629},{"id":1626,"depth":729,"text":837},"2026-03-16T00:00:00.000Z","We built an open source MCP server that reads and writes metadata tags in MP3, FLAC, and OGG files directly from Claude Code. Here is how it works.",[9707,9710,9713,9715],{"question":9708,"answer":9709},"What is mtl-metadata-mcp?","It is an open source MCP (Model Context Protocol) server that lets Claude Code read and write metadata tags like title, artist, album, date, genre, and ISRC in audio files.",{"question":9711,"answer":9712},"What audio formats are supported?","MP3 (ID3v2 tags), FLAC (Vorbis comments), and OGG (Vorbis comments).",{"question":9714,"answer":8725},"How do I install mtl-metadata-mcp?",{"question":9716,"answer":9717},"What is an ISRC code?","The International Standard Recording Code (ISRC) is a unique identifier assigned to each sound recording, used globally for tracking royalties and rights.",{"src":9719},"/images/blog/musictechlab_blog_mtl-metadata-mcp.webp",{"enabled":738,"items":9721},[9722,9724,9726,9728],{"text":9723,"icon":9547},"Read, write, scan, and clear audio metadata in MP3, FLAC, and OGG from natural language.",{"text":9725,"icon":7560},"Directory scanning flags files with missing ISRC codes, genres, or other required tags.",{"text":9727,"icon":5365},"The entire codebase is under 200 lines of core logic with 14 automated tests.",{"text":9729,"icon":1723},"MIT licensed and open source; installs with Poetry and registers with Claude Code.",{},{"title":9732,"description":9733},"mtl-metadata-mcp: Audio Metadata MCP Server | MusicTech Lab","Open source MCP server for reading and embedding metadata in MP3, FLAC, and OGG files. Built for Claude Code by MusicTech Lab.",[8747,3857,1626,8749,764,9735,3856],"ID3","CP2xAS2yFqouqpSWlZPeceXbI6TnPGJVuje3o0ef4_Y",{"id":9738,"title":120,"authors":9739,"badge":9742,"body":9743,"category":731,"client":723,"date":11594,"description":11595,"extension":734,"faq":11596,"featured":69,"featuredOrder":723,"hidden":69,"image":11606,"keyTakeaways":11608,"meta":11620,"navigation":738,"path":121,"seo":11621,"status":723,"stem":122,"tags":11624,"teaser":723,"__hash__":11629},"posts/blog/music-data/c2pa-and-ddex-authenticity-meets-rights-in-the-age-of-ai-music.md",[9740],{"name":834,"to":720,"avatar":9741},{"src":722},{"label":1745,"color":1746},{"type":725,"value":9744,"toc":11567},[9745,9748,9764,9768,9771,9775,9778,9795,9802,9813,9817,9820,9848,9852,9855,9859,9862,9930,9935,9939,9942,9974,9981,9985,10151,10155,10158,10204,10208,10215,10218,10229,10233,10236,10240,10243,10254,10258,10261,10272,10285,10289,10292,10407,10418,10422,10425,10569,10573,10576,10585,10590,10607,10624,10628,10632,10638,11133,11137,11152,11434,11445,11449,11515,11519,11529,11532,11535,11561,11564],[842,9746,9747],{},"A track lands on a streaming platform. It sounds like a well-known artist, but was it actually recorded by them -or generated by an AI model trained on their catalog? And regardless of origin, who should receive the royalties?",[842,9749,9750,9751,1589,9754,9757,9758,1589,9761,861],{},"These two questions -",[996,9752,9753],{},"\"is this real?\"",[996,9755,9756],{},"\"who gets paid?\""," -sit at the heart of the music industry's biggest challenges today. Two very different standards are stepping up to answer them: ",[996,9759,9760],{},"C2PA",[996,9762,9763],{},"DDEX",[863,9765,9767],{"id":9766},"what-is-c2pa","What is C2PA?",[842,9769,9770],{},"C2PA (Coalition for Content Provenance and Authenticity) is an open standard for proving the origin and history of digital content. Think of it as a tamper-proof seal for media files: images, video, audio, and documents.",[1074,9772,9774],{"id":9773},"how-it-works","How it works",[842,9776,9777],{},"The standard operates through three core mechanisms:",[1045,9779,9781,9786,9791],{"className":9780},[1048,1049,1050,1051,1052],[1054,9782],{"description":9783,"icon":9784,"title":9785},"Cryptographically signed metadata embedded in files. They record who created or edited the content, what tools were used, and when the changes happened.","i-lucide-fingerprint","Content Credentials",[1054,9787],{"description":9788,"icon":9789,"title":9790},"Structured metadata containers attached to content. Each manifest holds assertions (claims about the content) and a digital signature that can be verified against a certificate chain.","i-lucide-file-check","Manifests",[1054,9792],{"description":9793,"icon":1062,"title":9794},"Cryptographic ties between the manifest and the actual content bytes. If someone tampers with the file, the binding breaks and verification fails.","Hard Bindings",[842,9796,9797,9798,9801],{},"Every time content is created or edited, a new manifest entry is added, forming a ",[996,9799,9800],{},"provenance chain",". A camera captures an image and signs it. An editor crops it and adds a new signed manifest. An AI model generates a track and labels it as AI-created. Each step is recorded and verifiable.",[1572,9803,9804],{},[842,9805,9806,9807,9812],{},"C2PA was ",[846,9808,9811],{"href":9809,"rel":9810},"https://c2pa.org/about/",[850],"founded by Adobe, Arm, Intel, Microsoft, and Truepic"," as a Joint Development Foundation project. It unifies two earlier initiatives: the Adobe-led Content Authenticity Initiative (CAI) and Project Origin, a Microsoft- and BBC-led effort tackling disinformation in digital news. The coalition has since grown to include Sony, Nikon, Google, OpenAI, and many others.",[1074,9814,9816],{"id":9815},"c2pa-for-audio","C2PA for audio",[842,9818,9819],{},"While C2PA gained traction in the image and video space first, audio support is growing. The spec already supports audio file formats, and the implications for music are significant:",[958,9821,9822,9836,9842],{},[961,9823,9824,9827,9828,9831,9832,9835],{},[996,9825,9826],{},"AI transparency"," -a generative model like Suno or Udio could embed a C2PA manifest with a ",[895,9829,9830],{},"digitalSourceType"," (defined by IPTC) of ",[895,9833,9834],{},"trainedAlgorithmicMedia",", the standard way to declare content as AI-generated. Additional assertions can capture model details and prompts",[961,9837,9838,9841],{},[996,9839,9840],{},"Recording provenance"," -a DAW or recording device could sign audio at the point of capture, creating a verifiable chain from microphone to master. We are just starting to see reference implementations where stems -with or without provenance -can be mixed into a final and saved with provenance that includes ingredients, but these are demos and PoCs. No DAW has committed to shipping C2PA support yet",[961,9843,9844,9847],{},[996,9845,9846],{},"Remix and sample tracking"," -each derivative work adds a manifest, linking back to source material",[863,9849,9851],{"id":9850},"what-is-ddex","What is DDEX?",[842,9853,9854],{},"DDEX (Digital Data Exchange) is a group of music industry players that builds standards for sharing data about music. While C2PA asks \"is this content real?\", DDEX asks \"who owns this, and how should they be paid?\"",[1074,9856,9858],{"id":9857},"the-ddex-standard-family","The DDEX standard family",[842,9860,9861],{},"DDEX isn't a single standard -it's a family of XML and JSON-based messaging formats:",[871,9863,9864,9873],{},[874,9865,9866],{},[877,9867,9868,9871],{},[880,9869,9870],{},"Standard",[880,9872,3021],{},[887,9874,9875,9886,9897,9908,9919],{},[877,9876,9877,9883],{},[892,9878,9879,9882],{},[996,9880,9881],{},"ERN"," (Electronic Release Notification)",[892,9884,9885],{},"Delivering releases from labels to DSPs",[877,9887,9888,9894],{},[892,9889,9890,9893],{},[996,9891,9892],{},"MWN"," (Musical Works Notification)",[892,9895,9896],{},"Communicating musical works data between publishers and societies",[877,9898,9899,9905],{},[892,9900,9901,9904],{},[996,9902,9903],{},"MEAD"," (Media Enrichment and Description)",[892,9906,9907],{},"Enriching metadata for discovery and curation",[877,9909,9910,9916],{},[892,9911,9912,9915],{},[996,9913,9914],{},"DSR"," (Digital Sales Reporting)",[892,9917,9918],{},"Reporting sales and streams back to rights holders",[877,9920,9921,9927],{},[892,9922,9923,9926],{},[996,9924,9925],{},"RDR"," (Recording Data and Rights)",[892,9928,9929],{},"Linking sound recordings to musical works and their rights holders",[1032,9931,9932],{},[842,9933,9934],{},"If you distribute music to Spotify, Apple Music, Amazon, or any major DSP, your distributor is almost certainly using DDEX ERN messages under the hood.",[1074,9936,9938],{"id":9937},"what-ddex-carries","What DDEX carries",[842,9940,9941],{},"A DDEX message typically contains:",[958,9943,9944,9950,9956,9962,9968],{},[961,9945,9946,9949],{},[996,9947,9948],{},"Release metadata"," -title, artist, label, UPC/EAN, genre, release date",[961,9951,9952,9955],{},[996,9953,9954],{},"Track-level data"," -ISRC codes, duration, contributors, territories",[961,9957,9958,9961],{},[996,9959,9960],{},"Rights and licensing"," -who owns what, in which territories, under which terms",[961,9963,9964,9967],{},[996,9965,9966],{},"Commercial terms"," -pricing, availability windows, pre-order dates",[961,9969,9970,9973],{},[996,9971,9972],{},"Royalty reporting"," -stream counts, revenue splits, payment details",[842,9975,9976,9977,9980],{},"In practice, this metadata arrives from every distributor in different formats and naming conventions. Even something as simple as a retailer name can appear ",[846,9978,9979],{"href":81},"830 different ways across sources",", which is why normalisation is a critical step before any of this data becomes useful.",[863,9982,9984],{"id":9983},"who-are-these-standards-designed-for","Who are these standards designed for?",[1045,9986,9988,10071],{"className":9987},[1048,1049,1765,1051,1052],[1054,9989,9993],{"description":9990,"icon":9991,"title":9992},"Anyone who creates, distributes, or consumes digital content.","i-lucide-building-2","C2PA -Who benefits",[871,9994,9995,10005],{},[874,9996,9997],{},[877,9998,9999,10002],{},[880,10000,10001],{},"Stakeholder",[880,10003,10004],{},"How they use C2PA",[887,10006,10007,10018,10029,10040,10050,10060],{},[877,10008,10009,10015],{},[892,10010,10011,10014],{},[996,10012,10013],{},"DAW vendors"," (Ableton, Logic, Pro Tools)",[892,10016,10017],{},"Sign audio at export to prove origin",[877,10019,10020,10026],{},[892,10021,10022,10025],{},[996,10023,10024],{},"AI music platforms"," (Suno, Udio)",[892,10027,10028],{},"Label outputs as AI-generated with model details",[877,10030,10031,10037],{},[892,10032,10033,10036],{},[996,10034,10035],{},"Streaming platforms"," (Spotify, Apple Music)",[892,10038,10039],{},"Verify content authenticity at ingest",[877,10041,10042,10047],{},[892,10043,10044],{},[996,10045,10046],{},"News organizations",[892,10048,10049],{},"Confirm audio/video hasn't been manipulated",[877,10051,10052,10057],{},[892,10053,10054],{},[996,10055,10056],{},"Creators and artists",[892,10058,10059],{},"Prove their work is original and human-made",[877,10061,10062,10068],{},[892,10063,10064,10067],{},[996,10065,10066],{},"Hardware manufacturers"," (Nikon, Sony)",[892,10069,10070],{},"Embed provenance at point of capture",[1054,10072,10075],{"description":10073,"icon":9547,"title":10074},"Music industry players who need to exchange rights and commercial data.","DDEX -Who benefits",[871,10076,10077,10086],{},[874,10078,10079],{},[877,10080,10081,10083],{},[880,10082,10001],{},[880,10084,10085],{},"How they use DDEX",[887,10087,10088,10098,10109,10120,10130,10141],{},[877,10089,10090,10095],{},[892,10091,10092],{},[996,10093,10094],{},"Record labels",[892,10096,10097],{},"Deliver releases and metadata to DSPs",[877,10099,10100,10106],{},[892,10101,10102,10105],{},[996,10103,10104],{},"Distributors"," (DistroKid, TuneCore)",[892,10107,10108],{},"Automate release delivery via ERN messages",[877,10110,10111,10117],{},[892,10112,10113,10116],{},[996,10114,10115],{},"DSPs"," (Spotify, Apple Music, Amazon)",[892,10118,10119],{},"Ingest releases with structured rights data",[877,10121,10122,10127],{},[892,10123,10124],{},[996,10125,10126],{},"Publishers",[892,10128,10129],{},"Communicate musical works ownership via MWN",[877,10131,10132,10138],{},[892,10133,10134,10137],{},[996,10135,10136],{},"Collecting societies"," (ASCAP, PRS, ZAIKS)",[892,10139,10140],{},"Process royalty claims and distributions",[877,10142,10143,10148],{},[892,10144,10145],{},[996,10146,10147],{},"Independent artists",[892,10149,10150],{},"Get paid correctly through standardized reporting",[863,10152,10154],{"id":10153},"the-fundamental-difference","The fundamental difference",[842,10156,10157],{},"Here's the core distinction:",[1045,10159,10161,10182],{"className":10160},[1048,1049,1765,1051,1052],[1054,10162,10165],{"description":10163,"icon":1057,"title":10164},"Was this content tampered with? Who created it? Was AI involved?","C2PA -Trust Layer",[958,10166,10167,10170,10173,10176,10179],{},[961,10168,10169],{},"Cryptographic proof of origin",[961,10171,10172],{},"Tamper-evident edit history",[961,10174,10175],{},"AI disclosure and labeling",[961,10177,10178],{},"Works on any digital media",[961,10180,10181],{},"PKI-based trust model (see below)",[1054,10183,10187],{"description":10184,"icon":10185,"title":10186},"Who owns the rights? How should royalties be split? Where can this be distributed?","i-lucide-receipt","DDEX -Commerce Layer",[958,10188,10189,10192,10195,10198,10201],{},[961,10190,10191],{},"Rights ownership and splits",[961,10193,10194],{},"Commercial metadata exchange",[961,10196,10197],{},"Royalty reporting and payment",[961,10199,10200],{},"Music industry specific",[961,10202,10203],{},"B2B contractual trust model",[1074,10205,10207],{"id":10206},"how-c2pa-trust-works-pki-in-plain-terms","How C2PA trust works: PKI in plain terms",[842,10209,10210,10211,10214],{},"C2PA relies on ",[996,10212,10213],{},"Public Key Infrastructure (PKI)",", the same trust system that secures HTTPS websites. Every app or device that creates content holds a private key and a certificate from a trusted authority. When a C2PA manifest is signed, anyone can check that signature against the certificate chain, up to a root Certificate Authority (CA). If it checks out, you know the manifest is untouched and you know who produced it. Change even a single byte of the file, and the signature breaks.",[842,10216,10217],{},"In practice, this means a DAW vendor like Ableton could obtain a C2PA certificate, sign every exported master, and any platform receiving that file can verify it came from Ableton's software, untouched.",[842,10219,10220,10221,10224,10225,10228],{},"They operate at different layers of the content lifecycle. C2PA is about the ",[996,10222,10223],{},"integrity of the content itself"," -its provenance and authenticity. DDEX is about the ",[996,10226,10227],{},"business logic surrounding the content"," -ownership, distribution, and compensation.",[863,10230,10232],{"id":10231},"why-ai-music-needs-both","Why AI music needs both",[842,10234,10235],{},"The rise of AI-generated music is exactly why both standards matter now.",[1074,10237,10239],{"id":10238},"the-authenticity-problem","The authenticity problem",[842,10241,10242],{},"When an AI model can generate a track that sounds identical to a human recording, platforms, listeners, and rights holders all need to know the origin. Without C2PA-style provenance:",[958,10244,10245,10248,10251],{},[961,10246,10247],{},"A generated track could be uploaded as an \"original recording\" and claim royalties under false pretenses",[961,10249,10250],{},"Training data attribution becomes impossible to verify",[961,10252,10253],{},"Deepfake audio of real artists erodes trust across the entire ecosystem",[1074,10255,10257],{"id":10256},"the-rights-problem","The rights problem",[842,10259,10260],{},"Even when AI origin is disclosed, the rights questions are hard:",[958,10262,10263,10266,10269],{},[961,10264,10265],{},"Who owns an AI-generated track -the prompter, the model operator, or the training data contributors?",[961,10267,10268],{},"If a model was trained on copyrighted recordings, how should those rights holders be compensated?",[961,10270,10271],{},"How do existing DDEX workflows handle a \"performer\" that is a language model?",[1901,10273,10274],{},[842,10275,10276,10277,5660,10279,7378,10281,10284],{},"Current DDEX schemas weren't built for AI-generated content. Fields like ",[895,10278,7923],{},[895,10280,8872],{},[895,10282,10283],{},"Contributor"," assume human creators. The industry will need to extend these standards, or build new ones, to handle AI provenance and attribution.",[1074,10286,10288],{"id":10287},"the-combined-solution","The combined solution",[842,10290,10291],{},"Imagine a future where a single music file carries both layers:",[1045,10293,10295,10352],{"className":10294},[1048,1049,1765,1051,1052],[1054,10296,10299],{"description":10297,"icon":1057,"title":10298},"Authenticity and provenance layer embedded in the file.","C2PA Manifest",[871,10300,10301,10309],{},[874,10302,10303],{},[877,10304,10305,10307],{},[880,10306,7736],{},[880,10308,7844],{},[887,10310,10311,10319,10328,10336,10344],{},[877,10312,10313,10316],{},[892,10314,10315],{},"Created by",[892,10317,10318],{},"SunoAI v4.0",[877,10320,10321,10323],{},[892,10322,9830],{},[892,10324,10325,10327],{},[895,10326,9834],{}," (IPTC)",[877,10329,10330,10333],{},[892,10331,10332],{},"Prompt",[892,10334,10335],{},"\"upbeat jazz fusion, 120 BPM\"",[877,10337,10338,10341],{},[892,10339,10340],{},"Training data",[892,10342,10343],{},"Licensed Dataset X",[877,10345,10346,10349],{},[892,10347,10348],{},"Signature",[892,10350,10351],{},"Valid (SunoAI cert)",[1054,10353,10356],{"description":10354,"icon":10185,"title":10355},"Commercial and rights layer for distribution and payment.","DDEX Metadata",[871,10357,10358,10366],{},[874,10359,10360],{},[877,10361,10362,10364],{},[880,10363,7736],{},[880,10365,7844],{},[887,10367,10368,10375,10383,10391,10399],{},[877,10369,10370,10372],{},[892,10371,3856],{},[892,10373,10374],{},"USXX42312345",[877,10376,10377,10380],{},[892,10378,10379],{},"Rights holder",[892,10381,10382],{},"Acme Music LLC",[877,10384,10385,10388],{},[892,10386,10387],{},"Royalty split",[892,10389,10390],{},"70% publisher / 30% AI",[877,10392,10393,10396],{},[892,10394,10395],{},"Territory",[892,10397,10398],{},"Worldwide",[877,10400,10401,10404],{},[892,10402,10403],{},"Distributor",[892,10405,10406],{},"DistroKid",[842,10408,10409,10410,10413,10414,10417],{},"The C2PA layer provides ",[996,10411,10412],{},"verifiable proof"," of how the content was created. The DDEX layer provides ",[996,10415,10416],{},"the commercial framework"," for distributing it and paying the right parties.",[863,10419,10421],{"id":10420},"technical-integration-points","Technical integration points",[842,10423,10424],{},"For developers and music tech teams thinking about implementation, here are the key integration points:",[1045,10426,10428,10470,10527],{"className":10427},[1048,1049,1050,1051,1052],[1054,10429,10432],{"description":10430,"icon":8800,"title":10431},"When a track arrives at a DSP or distributor, run these checks in sequence.","Ingest Pipeline",[871,10433,10434,10444],{},[874,10435,10436],{},[877,10437,10438,10441],{},[880,10439,10440],{},"Step",[880,10442,10443],{},"Action",[887,10445,10446,10454,10462],{},[877,10447,10448,10451],{},[892,10449,10450],{},"Validate C2PA",[892,10452,10453],{},"Check signature chain, verify content integrity, extract provenance",[877,10455,10456,10459],{},[892,10457,10458],{},"Parse DDEX",[892,10460,10461],{},"Extract rights, contributors, and commercial terms",[877,10463,10464,10467],{},[892,10465,10466],{},"Cross-reference",[892,10468,10469],{},"Flag mismatches (e.g. C2PA says \"AI-generated\" but DDEX lists a human performer)",[1054,10471,10475],{"description":10472,"icon":10473,"title":10474},"C2PA provenance data can automatically populate DDEX fields.","i-lucide-merge","Metadata Enrichment",[871,10476,10477,10487],{},[874,10478,10479],{},[877,10480,10481,10484],{},[880,10482,10483],{},"C2PA Source",[880,10485,10486],{},"DDEX Target",[887,10488,10489,10504,10513],{},[877,10490,10491,10496],{},[892,10492,10493],{},[895,10494,10495],{},"creator",[892,10497,10498,10501,10502],{},[895,10499,10500],{},"DisplayArtist"," / ",[895,10503,10283],{},[877,10505,10506,10510],{},[892,10507,10508],{},[895,10509,9830],{},[892,10511,10512],{},"Extension field for AI disclosure",[877,10514,10515,10521],{},[892,10516,10517,10520],{},[895,10518,10519],{},"source material"," refs",[892,10522,10523,10526],{},[895,10524,10525],{},"RelatedRelease"," links",[1054,10528,10532],{"description":10529,"icon":10530,"title":10531},"Before distributing a track, a platform should verify both layers.","i-lucide-check-circle","Rights Verification",[871,10533,10534,10543],{},[874,10535,10536],{},[877,10537,10538,10541],{},[880,10539,10540],{},"Check",[880,10542,3021],{},[887,10544,10545,10553,10561],{},[877,10546,10547,10550],{},[892,10548,10549],{},"C2PA chain",[892,10551,10552],{},"Confirm submitter has legitimate access to the content",[877,10554,10555,10558],{},[892,10556,10557],{},"DDEX rights",[892,10559,10560],{},"Confirm distribution is authorized for the target territory",[877,10562,10563,10566],{},[892,10564,10565],{},"Training data refs",[892,10567,10568],{},"Verify licensing compliance for AI-generated content",[863,10570,10572],{"id":10571},"whats-happening-now","What's happening now",[842,10574,10575],{},"Both standards are actively evolving:",[842,10577,10578,10580,10581,10584],{},[996,10579,9760],{}," released version 2.3 of the specification and the ecosystem is growing rapidly. The ",[895,10582,10583],{},"c2patool"," CLI and libraries in Rust, JavaScript, and Python make it possible to read and write C2PA manifests programmatically, though writing manifests correctly is non-trivial in practice.",[842,10586,10587,10589],{},[996,10588,9763],{}," continues to refine its standards. ERN 4.3 is the latest release notification format, and there are ongoing discussions within the consortium about how to handle AI-generated content within existing schemas.",[1572,10591,10592],{},[842,10593,10594,10595,10597,10598,10603,10604,10606],{},"The C2PA spec requires every asset to include a ",[895,10596,9830],{}," as defined by ",[846,10599,10602],{"href":10600,"rel":10601},"https://iptc.org/",[850],"IPTC",". For AI-generated content, the value is ",[895,10605,9834],{},". There are additional assertions available for AI content, but in practice consumption and verification tooling is still catching up -much of this remains theoretical for now.",[1032,10608,10609],{},[842,10610,10611,10612,10617,10618,10623],{},"If you're building music tech infrastructure, start experimenting with C2PA now. The ",[846,10613,10616],{"href":10614,"rel":10615},"https://github.com/contentauth/c2pa-rs",[850],"c2pa-rs"," Rust crate and ",[846,10619,10622],{"href":10620,"rel":10621},"https://github.com/contentauth/c2pa-node",[850],"c2pa-node"," JavaScript library are production-ready and well-documented. You can get quite far using the development certificate for testing.",[863,10625,10627],{"id":10626},"code-examples","Code examples",[1074,10629,10631],{"id":10630},"reading-a-c2pa-manifest-with-python","Reading a C2PA manifest with Python",[842,10633,5119,10634,10637],{},[895,10635,10636],{},"c2pa-python"," library lets you read and validate Content Credentials from any supported file:",[1013,10639,10642],{"className":1368,"code":10640,"filename":10641,"language":1250,"meta":728,"style":728},"import c2pa\n\n# Read the C2PA manifest from an audio file\nreader = c2pa.Reader.from_file(\"track.wav\")\n\n# Get the active manifest (most recent signer)\nmanifest = reader.get_active_manifest()\n\nprint(f\"Title: {manifest['title']}\")\nprint(f\"Format: {manifest['format']}\")\n\n# Check assertions -was this AI-generated?\nfor assertion in manifest.get(\"assertions\", []):\n    if assertion[\"label\"] == \"c2pa.actions\":\n        for action in assertion[\"data\"][\"actions\"]:\n            print(f\"Action: {action['action']}\")\n            if \"softwareAgent\" in action:\n                print(f\"Software: {action['softwareAgent']}\")\n            # digitalSourceType is required by the spec (IPTC vocabulary)\n            # trainedAlgorithmicMedia = AI-generated content\n            if \"digitalSourceType\" in action:\n                print(f\"Source type: {action['digitalSourceType']}\")\n\n# Validate the signature chain\nvalidation = reader.validation_status\nif not validation:\n    print(\"Signature: VALID\")\nelse:\n    for status in validation:\n        print(f\"Issue: {status['code']}\")\n","read_c2pa.py",[895,10643,10644,10651,10655,10660,10691,10695,10700,10717,10721,10755,10786,10790,10795,10827,10857,10889,10922,10940,10972,10977,10982,10998,11029,11033,11038,11052,11064,11080,11087,11100],{"__ignoreMap":728},[1086,10645,10646,10648],{"class":1088,"line":1089},[1086,10647,6503],{"class":1423},[1086,10649,10650],{"class":1436}," c2pa\n",[1086,10652,10653],{"class":1088,"line":729},[1086,10654,3390],{"emptyLinePlaceholder":738},[1086,10656,10657],{"class":1088,"line":1112},[1086,10658,10659],{"class":1427},"# Read the C2PA manifest from an audio file\n",[1086,10661,10662,10665,10667,10670,10672,10675,10677,10680,10682,10684,10687,10689],{"class":1088,"line":1181},[1086,10663,10664],{"class":1436},"reader ",[1086,10666,1440],{"class":1146},[1086,10668,10669],{"class":1436}," c2pa",[1086,10671,861],{"class":1146},[1086,10673,10674],{"class":4109},"Reader",[1086,10676,861],{"class":1146},[1086,10678,10679],{"class":1105},"from_file",[1086,10681,1398],{"class":1146},[1086,10683,1159],{"class":1146},[1086,10685,10686],{"class":1096},"track.wav",[1086,10688,1159],{"class":1146},[1086,10690,1455],{"class":1146},[1086,10692,10693],{"class":1088,"line":1205},[1086,10694,3390],{"emptyLinePlaceholder":738},[1086,10696,10697],{"class":1088,"line":1276},[1086,10698,10699],{"class":1427},"# Get the active manifest (most recent signer)\n",[1086,10701,10702,10705,10707,10710,10712,10715],{"class":1088,"line":1282},[1086,10703,10704],{"class":1436},"manifest ",[1086,10706,1440],{"class":1146},[1086,10708,10709],{"class":1436}," reader",[1086,10711,861],{"class":1146},[1086,10713,10714],{"class":1105},"get_active_manifest",[1086,10716,1387],{"class":1146},[1086,10718,10719],{"class":1088,"line":1288},[1086,10720,3390],{"emptyLinePlaceholder":738},[1086,10722,10723,10726,10728,10730,10733,10735,10738,10740,10743,10745,10747,10749,10751,10753],{"class":1088,"line":2685},[1086,10724,10725],{"class":1105},"print",[1086,10727,1398],{"class":1146},[1086,10729,5962],{"class":1155},[1086,10731,10732],{"class":1096},"\"Title: ",[1086,10734,4409],{"class":1187},[1086,10736,10737],{"class":1105},"manifest",[1086,10739,4340],{"class":1146},[1086,10741,10742],{"class":1146},"'",[1086,10744,9069],{"class":1096},[1086,10746,10742],{"class":1146},[1086,10748,4420],{"class":1146},[1086,10750,4423],{"class":1187},[1086,10752,1159],{"class":1096},[1086,10754,1455],{"class":1146},[1086,10756,10757,10759,10761,10763,10766,10768,10770,10772,10774,10776,10778,10780,10782,10784],{"class":1088,"line":2700},[1086,10758,10725],{"class":1105},[1086,10760,1398],{"class":1146},[1086,10762,5962],{"class":1155},[1086,10764,10765],{"class":1096},"\"Format: ",[1086,10767,4409],{"class":1187},[1086,10769,10737],{"class":1105},[1086,10771,4340],{"class":1146},[1086,10773,10742],{"class":1146},[1086,10775,8973],{"class":1096},[1086,10777,10742],{"class":1146},[1086,10779,4420],{"class":1146},[1086,10781,4423],{"class":1187},[1086,10783,1159],{"class":1096},[1086,10785,1455],{"class":1146},[1086,10787,10788],{"class":1088,"line":3398},[1086,10789,3390],{"emptyLinePlaceholder":738},[1086,10791,10792],{"class":1088,"line":1715},[1086,10793,10794],{"class":1427},"# Check assertions -was this AI-generated?\n",[1086,10796,10797,10800,10803,10805,10808,10810,10813,10815,10817,10820,10822,10824],{"class":1088,"line":3409},[1086,10798,10799],{"class":1423},"for",[1086,10801,10802],{"class":1436}," assertion ",[1086,10804,5931],{"class":1423},[1086,10806,10807],{"class":1436}," manifest",[1086,10809,861],{"class":1146},[1086,10811,10812],{"class":1105},"get",[1086,10814,1398],{"class":1146},[1086,10816,1159],{"class":1146},[1086,10818,10819],{"class":1096},"assertions",[1086,10821,1159],{"class":1146},[1086,10823,1227],{"class":1146},[1086,10825,10826],{"class":1146}," []):\n",[1086,10828,10829,10831,10834,10836,10838,10841,10843,10845,10848,10850,10853,10855],{"class":1088,"line":3415},[1086,10830,6474],{"class":1423},[1086,10832,10833],{"class":1436}," assertion",[1086,10835,4340],{"class":1146},[1086,10837,1159],{"class":1146},[1086,10839,10840],{"class":1096},"label",[1086,10842,1159],{"class":1146},[1086,10844,4420],{"class":1146},[1086,10846,10847],{"class":1146}," ==",[1086,10849,1195],{"class":1146},[1086,10851,10852],{"class":1096},"c2pa.actions",[1086,10854,1159],{"class":1146},[1086,10856,1418],{"class":1146},[1086,10858,10859,10861,10864,10866,10868,10870,10872,10874,10876,10879,10881,10884,10886],{"class":1088,"line":3421},[1086,10860,6903],{"class":1423},[1086,10862,10863],{"class":1436}," action ",[1086,10865,5931],{"class":1423},[1086,10867,10833],{"class":1436},[1086,10869,4340],{"class":1146},[1086,10871,1159],{"class":1146},[1086,10873,4337],{"class":1096},[1086,10875,1159],{"class":1146},[1086,10877,10878],{"class":1146},"][",[1086,10880,1159],{"class":1146},[1086,10882,10883],{"class":1096},"actions",[1086,10885,1159],{"class":1146},[1086,10887,10888],{"class":1146},"]:\n",[1086,10890,10891,10894,10896,10898,10901,10903,10906,10908,10910,10912,10914,10916,10918,10920],{"class":1088,"line":3427},[1086,10892,10893],{"class":1105},"            print",[1086,10895,1398],{"class":1146},[1086,10897,5962],{"class":1155},[1086,10899,10900],{"class":1096},"\"Action: ",[1086,10902,4409],{"class":1187},[1086,10904,10905],{"class":1105},"action",[1086,10907,4340],{"class":1146},[1086,10909,10742],{"class":1146},[1086,10911,10905],{"class":1096},[1086,10913,10742],{"class":1146},[1086,10915,4420],{"class":1146},[1086,10917,4423],{"class":1187},[1086,10919,1159],{"class":1096},[1086,10921,1455],{"class":1146},[1086,10923,10924,10926,10928,10931,10933,10935,10938],{"class":1088,"line":3433},[1086,10925,6918],{"class":1423},[1086,10927,1195],{"class":1146},[1086,10929,10930],{"class":1096},"softwareAgent",[1086,10932,1159],{"class":1146},[1086,10934,6858],{"class":1146},[1086,10936,10937],{"class":1436}," action",[1086,10939,1418],{"class":1146},[1086,10941,10942,10945,10947,10949,10952,10954,10956,10958,10960,10962,10964,10966,10968,10970],{"class":1088,"line":3439},[1086,10943,10944],{"class":1105},"                print",[1086,10946,1398],{"class":1146},[1086,10948,5962],{"class":1155},[1086,10950,10951],{"class":1096},"\"Software: ",[1086,10953,4409],{"class":1187},[1086,10955,10905],{"class":1105},[1086,10957,4340],{"class":1146},[1086,10959,10742],{"class":1146},[1086,10961,10930],{"class":1096},[1086,10963,10742],{"class":1146},[1086,10965,4420],{"class":1146},[1086,10967,4423],{"class":1187},[1086,10969,1159],{"class":1096},[1086,10971,1455],{"class":1146},[1086,10973,10974],{"class":1088,"line":3444},[1086,10975,10976],{"class":1427},"            # digitalSourceType is required by the spec (IPTC vocabulary)\n",[1086,10978,10979],{"class":1088,"line":3450},[1086,10980,10981],{"class":1427},"            # trainedAlgorithmicMedia = AI-generated content\n",[1086,10983,10984,10986,10988,10990,10992,10994,10996],{"class":1088,"line":3456},[1086,10985,6918],{"class":1423},[1086,10987,1195],{"class":1146},[1086,10989,9830],{"class":1096},[1086,10991,1159],{"class":1146},[1086,10993,6858],{"class":1146},[1086,10995,10937],{"class":1436},[1086,10997,1418],{"class":1146},[1086,10999,11000,11002,11004,11006,11009,11011,11013,11015,11017,11019,11021,11023,11025,11027],{"class":1088,"line":3462},[1086,11001,10944],{"class":1105},[1086,11003,1398],{"class":1146},[1086,11005,5962],{"class":1155},[1086,11007,11008],{"class":1096},"\"Source type: ",[1086,11010,4409],{"class":1187},[1086,11012,10905],{"class":1105},[1086,11014,4340],{"class":1146},[1086,11016,10742],{"class":1146},[1086,11018,9830],{"class":1096},[1086,11020,10742],{"class":1146},[1086,11022,4420],{"class":1146},[1086,11024,4423],{"class":1187},[1086,11026,1159],{"class":1096},[1086,11028,1455],{"class":1146},[1086,11030,11031],{"class":1088,"line":3467},[1086,11032,3390],{"emptyLinePlaceholder":738},[1086,11034,11035],{"class":1088,"line":3473},[1086,11036,11037],{"class":1427},"# Validate the signature chain\n",[1086,11039,11040,11043,11045,11047,11049],{"class":1088,"line":3479},[1086,11041,11042],{"class":1436},"validation ",[1086,11044,1440],{"class":1146},[1086,11046,10709],{"class":1436},[1086,11048,861],{"class":1146},[1086,11050,11051],{"class":4109},"validation_status\n",[1086,11053,11054,11057,11059,11062],{"class":1088,"line":3485},[1086,11055,11056],{"class":1423},"if",[1086,11058,6803],{"class":1146},[1086,11060,11061],{"class":1436}," validation",[1086,11063,1418],{"class":1146},[1086,11065,11066,11069,11071,11073,11076,11078],{"class":1088,"line":3491},[1086,11067,11068],{"class":1105},"    print",[1086,11070,1398],{"class":1146},[1086,11072,1159],{"class":1146},[1086,11074,11075],{"class":1096},"Signature: VALID",[1086,11077,1159],{"class":1146},[1086,11079,1455],{"class":1146},[1086,11081,11082,11085],{"class":1088,"line":3497},[1086,11083,11084],{"class":1423},"else",[1086,11086,1418],{"class":1146},[1086,11088,11089,11091,11094,11096,11098],{"class":1088,"line":3503},[1086,11090,5925],{"class":1423},[1086,11092,11093],{"class":1436}," status ",[1086,11095,5931],{"class":1423},[1086,11097,11061],{"class":1436},[1086,11099,1418],{"class":1146},[1086,11101,11102,11105,11107,11109,11112,11114,11117,11119,11121,11123,11125,11127,11129,11131],{"class":1088,"line":3509},[1086,11103,11104],{"class":1105},"        print",[1086,11106,1398],{"class":1146},[1086,11108,5962],{"class":1155},[1086,11110,11111],{"class":1096},"\"Issue: ",[1086,11113,4409],{"class":1187},[1086,11115,11116],{"class":1105},"status",[1086,11118,4340],{"class":1146},[1086,11120,10742],{"class":1146},[1086,11122,895],{"class":1096},[1086,11124,10742],{"class":1146},[1086,11126,4420],{"class":1146},[1086,11128,4423],{"class":1187},[1086,11130,1159],{"class":1096},[1086,11132,1455],{"class":1146},[1074,11134,11136],{"id":11135},"ddex-ern-message-simplified","DDEX ERN message (simplified)",[842,11138,11139,11140,11143,11144,11147,11148,11151],{},"The core of a DDEX Electronic Release Notification boils down to three blocks: ",[996,11141,11142],{},"what"," is being released, ",[996,11145,11146],{},"who"," performs it, and ",[996,11149,11150],{},"where/how"," it can be streamed:",[1013,11153,11158],{"className":11154,"code":11155,"filename":11156,"language":11157,"meta":728,"style":728},"language-xml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003CNewReleaseMessage>\n  \u003C!-- WHAT: the release -->\n  \u003CRelease>\n    \u003CICPN>0123456789012\u003C/ICPN>\n    \u003CTitle>Upbeat Jazz Fusion\u003C/Title>\n    \u003CArtist>Acme Music AI\u003C/Artist>\n    \u003CTerritory>Worldwide\u003C/Territory>\n  \u003C/Release>\n\n  \u003C!-- WHO: the sound recording -->\n  \u003CSoundRecording>\n    \u003CISRC>USXX42312345\u003C/ISRC>\n    \u003CDuration>PT3M24S\u003C/Duration>\n    \u003CArtistRole>MainArtist\u003C/ArtistRole>  \u003C!-- No \"AI\" option here -->\n  \u003C/SoundRecording>\n\n  \u003C!-- HOW: the deal terms -->\n  \u003CDeal>\n    \u003CModel>SubscriptionModel\u003C/Model>\n    \u003CUseType>OnDemandStream\u003C/UseType>\n    \u003CStartDate>2026-03-13\u003C/StartDate>\n  \u003C/Deal>\n\u003C/NewReleaseMessage>\n","ern_release.xml","xml",[895,11159,11160,11171,11176,11186,11206,11223,11240,11256,11265,11269,11274,11283,11299,11317,11338,11346,11350,11355,11364,11382,11400,11418,11426],{"__ignoreMap":728},[1086,11161,11162,11165,11168],{"class":1088,"line":1089},[1086,11163,11164],{"class":1146},"\u003C",[1086,11166,11167],{"class":4109},"NewReleaseMessage",[1086,11169,11170],{"class":1146},">\n",[1086,11172,11173],{"class":1088,"line":729},[1086,11174,11175],{"class":1427},"  \u003C!-- WHAT: the release -->\n",[1086,11177,11178,11181,11184],{"class":1088,"line":1112},[1086,11179,11180],{"class":1146},"  \u003C",[1086,11182,11183],{"class":4109},"Release",[1086,11185,11170],{"class":1146},[1086,11187,11188,11191,11194,11196,11199,11202,11204],{"class":1088,"line":1181},[1086,11189,11190],{"class":1146},"    \u003C",[1086,11192,11193],{"class":4109},"ICPN",[1086,11195,2694],{"class":1146},[1086,11197,11198],{"class":1436},"0123456789012",[1086,11200,11201],{"class":1146},"\u003C/",[1086,11203,11193],{"class":4109},[1086,11205,11170],{"class":1146},[1086,11207,11208,11210,11212,11214,11217,11219,11221],{"class":1088,"line":1205},[1086,11209,11190],{"class":1146},[1086,11211,8859],{"class":4109},[1086,11213,2694],{"class":1146},[1086,11215,11216],{"class":1436},"Upbeat Jazz Fusion",[1086,11218,11201],{"class":1146},[1086,11220,8859],{"class":4109},[1086,11222,11170],{"class":1146},[1086,11224,11225,11227,11229,11231,11234,11236,11238],{"class":1088,"line":1276},[1086,11226,11190],{"class":1146},[1086,11228,7923],{"class":4109},[1086,11230,2694],{"class":1146},[1086,11232,11233],{"class":1436},"Acme Music AI",[1086,11235,11201],{"class":1146},[1086,11237,7923],{"class":4109},[1086,11239,11170],{"class":1146},[1086,11241,11242,11244,11246,11248,11250,11252,11254],{"class":1088,"line":1282},[1086,11243,11190],{"class":1146},[1086,11245,10395],{"class":4109},[1086,11247,2694],{"class":1146},[1086,11249,10398],{"class":1436},[1086,11251,11201],{"class":1146},[1086,11253,10395],{"class":4109},[1086,11255,11170],{"class":1146},[1086,11257,11258,11261,11263],{"class":1088,"line":1288},[1086,11259,11260],{"class":1146},"  \u003C/",[1086,11262,11183],{"class":4109},[1086,11264,11170],{"class":1146},[1086,11266,11267],{"class":1088,"line":2685},[1086,11268,3390],{"emptyLinePlaceholder":738},[1086,11270,11271],{"class":1088,"line":2700},[1086,11272,11273],{"class":1427},"  \u003C!-- WHO: the sound recording -->\n",[1086,11275,11276,11278,11281],{"class":1088,"line":3398},[1086,11277,11180],{"class":1146},[1086,11279,11280],{"class":4109},"SoundRecording",[1086,11282,11170],{"class":1146},[1086,11284,11285,11287,11289,11291,11293,11295,11297],{"class":1088,"line":1715},[1086,11286,11190],{"class":1146},[1086,11288,3856],{"class":4109},[1086,11290,2694],{"class":1146},[1086,11292,10374],{"class":1436},[1086,11294,11201],{"class":1146},[1086,11296,3856],{"class":4109},[1086,11298,11170],{"class":1146},[1086,11300,11301,11303,11306,11308,11311,11313,11315],{"class":1088,"line":3409},[1086,11302,11190],{"class":1146},[1086,11304,11305],{"class":4109},"Duration",[1086,11307,2694],{"class":1146},[1086,11309,11310],{"class":1436},"PT3M24S",[1086,11312,11201],{"class":1146},[1086,11314,11305],{"class":4109},[1086,11316,11170],{"class":1146},[1086,11318,11319,11321,11324,11326,11329,11331,11333,11335],{"class":1088,"line":3415},[1086,11320,11190],{"class":1146},[1086,11322,11323],{"class":4109},"ArtistRole",[1086,11325,2694],{"class":1146},[1086,11327,11328],{"class":1436},"MainArtist",[1086,11330,11201],{"class":1146},[1086,11332,11323],{"class":4109},[1086,11334,2694],{"class":1146},[1086,11336,11337],{"class":1427},"  \u003C!-- No \"AI\" option here -->\n",[1086,11339,11340,11342,11344],{"class":1088,"line":3421},[1086,11341,11260],{"class":1146},[1086,11343,11280],{"class":4109},[1086,11345,11170],{"class":1146},[1086,11347,11348],{"class":1088,"line":3427},[1086,11349,3390],{"emptyLinePlaceholder":738},[1086,11351,11352],{"class":1088,"line":3433},[1086,11353,11354],{"class":1427},"  \u003C!-- HOW: the deal terms -->\n",[1086,11356,11357,11359,11362],{"class":1088,"line":3439},[1086,11358,11180],{"class":1146},[1086,11360,11361],{"class":4109},"Deal",[1086,11363,11170],{"class":1146},[1086,11365,11366,11368,11371,11373,11376,11378,11380],{"class":1088,"line":3444},[1086,11367,11190],{"class":1146},[1086,11369,11370],{"class":4109},"Model",[1086,11372,2694],{"class":1146},[1086,11374,11375],{"class":1436},"SubscriptionModel",[1086,11377,11201],{"class":1146},[1086,11379,11370],{"class":4109},[1086,11381,11170],{"class":1146},[1086,11383,11384,11386,11389,11391,11394,11396,11398],{"class":1088,"line":3450},[1086,11385,11190],{"class":1146},[1086,11387,11388],{"class":4109},"UseType",[1086,11390,2694],{"class":1146},[1086,11392,11393],{"class":1436},"OnDemandStream",[1086,11395,11201],{"class":1146},[1086,11397,11388],{"class":4109},[1086,11399,11170],{"class":1146},[1086,11401,11402,11404,11407,11409,11412,11414,11416],{"class":1088,"line":3456},[1086,11403,11190],{"class":1146},[1086,11405,11406],{"class":4109},"StartDate",[1086,11408,2694],{"class":1146},[1086,11410,11411],{"class":1436},"2026-03-13",[1086,11413,11201],{"class":1146},[1086,11415,11406],{"class":4109},[1086,11417,11170],{"class":1146},[1086,11419,11420,11422,11424],{"class":1088,"line":3462},[1086,11421,11260],{"class":1146},[1086,11423,11361],{"class":4109},[1086,11425,11170],{"class":1146},[1086,11427,11428,11430,11432],{"class":1088,"line":3467},[1086,11429,11201],{"class":1146},[1086,11431,11167],{"class":4109},[1086,11433,11170],{"class":1146},[1572,11435,11436],{},[842,11437,11438,11439,11441,11442,11444],{},"Notice how the DDEX schema has no concept of \"AI-generated.\" The ",[895,11440,11323],{}," is ",[895,11443,11328],{},", which traditionally means a human performer. This is exactly the gap that combining C2PA provenance with DDEX metadata would fill.",[1074,11446,11448],{"id":11447},"real-world-use-cases","Real-world use cases",[1045,11450,11452,11467,11483,11499],{"className":11451},[1048,1049,1765,1051,1052],[1054,11453,11456],{"description":11454,"icon":5315,"title":11455},"A service like ElevenLabs or Kits.AI generates a vocal track using a licensed artist voice model.","AI vocal cloning",[958,11457,11458,11461,11464],{},[961,11459,11460],{},"C2PA manifest declares the output as AI-generated, referencing the voice model license",[961,11462,11463],{},"DDEX metadata routes royalties to both the platform and the original voice owner",[961,11465,11466],{},"DSPs can surface an \"AI-generated vocal\" label to listeners",[1054,11468,11472],{"description":11469,"icon":11470,"title":11471},"A music supervisor finds a track on a sync platform and needs to clear rights quickly.","i-lucide-radio","Sync licensing for film/TV",[958,11473,11474,11477,11480],{},[961,11475,11476],{},"C2PA chain confirms the track is an unaltered original from a known studio",[961,11478,11479],{},"DDEX rights data shows territory clearance, splits, and one-stop licensing",[961,11481,11482],{},"The supervisor can verify authenticity and clear rights in a single workflow",[1054,11484,11488],{"description":11485,"icon":11486,"title":11487},"A producer builds a track using stems from Splice or Tracklib.","i-lucide-disc-3","Sample-based production",[958,11489,11490,11493,11496],{},[961,11491,11492],{},"Each stem carries a C2PA manifest linking to its original recording session",[961,11494,11495],{},"The DAW adds a new manifest referencing all source stems on export",[961,11497,11498],{},"DDEX metadata captures royalty splits between the producer and sample owners",[1054,11500,11504],{"description":11501,"icon":11502,"title":11503},"A streaming platform receives a submission that sounds like a major artist from an unknown account.","i-lucide-shield-alert","Deepfake detection at DSPs",[958,11505,11506,11509,11512],{},[961,11507,11508],{},"No C2PA manifest present, or the manifest shows AI generation with no artist license",[961,11510,11511],{},"DDEX metadata claims the submitter as the performer, contradicting the audio fingerprint",[961,11513,11514],{},"Automated pipeline flags the submission for human review before it goes live",[863,11516,11518],{"id":11517},"looking-ahead","Looking ahead",[842,11520,11521,11522,1589,11525,11528],{},"The music industry is approaching a tipping point. As AI-generated content floods platforms, the demand for both ",[996,11523,11524],{},"authenticity verification",[996,11526,11527],{},"rights management"," will only grow.",[842,11530,11531],{},"The platforms that win will be the ones that treat provenance and rights as two sides of the same coin. C2PA and DDEX aren't competitors. Together, they could form the trust layer the music industry needs.",[842,11533,11534],{},"For music tech builders, the action items are clear:",[991,11536,11537,11543,11549,11555],{},[961,11538,11539,11542],{},[996,11540,11541],{},"Embed C2PA manifests"," at the point of content creation -whether that's a DAW, an AI model, or a recording device",[961,11544,11545,11548],{},[996,11546,11547],{},"Extend DDEX workflows"," to consume and reference C2PA provenance data",[961,11550,11551,11554],{},[996,11552,11553],{},"Build cross-validation"," into ingest pipelines to catch mismatches between authenticity claims and rights declarations",[961,11556,11557,11560],{},[996,11558,11559],{},"Engage with both consortiums"," -the standards are still evolving, and the music industry's voice matters in shaping them",[842,11562,11563],{},"The question isn't whether these standards will converge -it's how quickly the industry can make it happen.",[1680,11565,11566],{},"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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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);}",{"title":728,"searchDepth":729,"depth":729,"links":11568},[11569,11573,11577,11578,11581,11586,11587,11588,11593],{"id":9766,"depth":729,"text":9767,"children":11570},[11571,11572],{"id":9773,"depth":1112,"text":9774},{"id":9815,"depth":1112,"text":9816},{"id":9850,"depth":729,"text":9851,"children":11574},[11575,11576],{"id":9857,"depth":1112,"text":9858},{"id":9937,"depth":1112,"text":9938},{"id":9983,"depth":729,"text":9984},{"id":10153,"depth":729,"text":10154,"children":11579},[11580],{"id":10206,"depth":1112,"text":10207},{"id":10231,"depth":729,"text":10232,"children":11582},[11583,11584,11585],{"id":10238,"depth":1112,"text":10239},{"id":10256,"depth":1112,"text":10257},{"id":10287,"depth":1112,"text":10288},{"id":10420,"depth":729,"text":10421},{"id":10571,"depth":729,"text":10572},{"id":10626,"depth":729,"text":10627,"children":11589},[11590,11591,11592],{"id":10630,"depth":1112,"text":10631},{"id":11135,"depth":1112,"text":11136},{"id":11447,"depth":1112,"text":11448},{"id":11517,"depth":729,"text":11518},"2026-03-13T00:00:00.000Z","C2PA proves content is real. DDEX ensures the right people get paid. Together, could they reshape how the music industry handles trust and rights in the AI era?",[11597,11600,11603],{"question":11598,"answer":11599},"What is the difference between C2PA and DDEX?","C2PA is a content authenticity standard that cryptographically proves who created digital content and whether it was modified. DDEX is a music industry standard for exchanging commercial metadata like rights ownership, royalty splits, and release information. C2PA answers 'is this real?' while DDEX answers 'who gets paid?'",{"question":11601,"answer":11602},"Can C2PA and DDEX work together?","Yes. A music file could carry both a C2PA manifest proving its origin and creation history, and DDEX metadata ensuring correct royalty distribution. Together they provide end-to-end trust -from authenticity to payment.",{"question":11604,"answer":11605},"Why do these standards matter for AI-generated music?","AI-generated tracks blur the line between human and machine creation. C2PA can transparently label AI-generated content, while DDEX can handle the complex rights questions around who owns and profits from AI-created music.",{"src":11607},"/images/blog/musictechlab_blog_c2pa-vs-ddex.webp",{"enabled":738,"items":11609},[11610,11612,11615,11618],{"text":11611,"icon":7495},"C2PA proves content origin with cryptographic signatures; DDEX handles rights and royalty splits.",{"text":11613,"icon":11614},"Current DDEX schemas have no concept of AI-generated content or non-human performers.","i-lucide-brain",{"text":11616,"icon":11617},"Combining both standards creates end-to-end trust from authenticity verification to payment.","i-lucide-handshake",{"text":11619,"icon":5365},"C2PA tools in Rust, JavaScript, and Python are production-ready for experimentation today.",{},{"title":11622,"description":11623},"C2PA & DDEX: Authenticity & Rights in AI Music","Compare C2PA and DDEX standards -how content provenance and rights metadata work together to solve trust and payment in the AI music era.",[9760,9763,11625,11626,11627,3857,11628],"AI music","content authenticity","music rights","music distribution","gAC7wVP0DrbqIReV_k8NunYO6AEVCudMp3VZZ9-1zfY",{"id":11631,"title":618,"authors":11632,"badge":11635,"body":11636,"category":756,"client":723,"date":12386,"description":12387,"extension":734,"faq":12388,"featured":738,"featuredOrder":3398,"hidden":69,"image":12400,"keyTakeaways":12403,"meta":12413,"navigation":738,"path":619,"seo":12414,"status":723,"stem":620,"tags":12417,"teaser":723,"__hash__":12421},"posts/blog/software-development/signnow-mcp-server-e-signatures-from-claude-code.md",[11633],{"name":834,"to":720,"avatar":11634},{"src":722},{"label":837,"color":838},{"type":725,"value":11637,"toc":12372},[11638,11645,11648,11655,11658,11664,11667,11669,11672,11794,11797,11801,11804,11810,11821,11823,11827,11861,11887,11891,11934,11938,11941,11980,11983,12209,12218,12222,12225,12255,12258,12267,12270,12275,12279,12282,12327,12332,12334,12369],[842,11639,11640,11641,11644],{},"A few weeks ago we published a deep dive on ",[846,11642,11643],{"href":543},"integrating SignNow e-signatures into a Django application",". That article covered the full server-side integration — OAuth2, document uploads, Celery tasks, webhooks — everything you need for a production signing workflow.",[842,11646,11647],{},"But we kept coming back to the same thought: what if you didn't need a web app at all? What if you could upload a contract, send it for signing, and check its status without ever leaving your terminal?",[842,11649,11650,11651,11654],{},"That's exactly what we built. Today we're open-sourcing ",[846,11652,1588],{"href":1586,"rel":11653},[850]," — a Model Context Protocol server that brings airSlate SignNow's e-signature capabilities directly into Claude Code.",[863,11656,8773],{"id":11657},"what-is-mcp",[842,11659,5119,11660,11663],{},[846,11661,1677],{"href":1675,"rel":11662},[850]," (MCP) is an open standard that lets AI assistants like Claude interact with external tools and services. Think of it as a plugin system — you register an MCP server, and Claude gains new abilities.",[842,11665,11666],{},"In our case, those abilities are e-signatures.",[863,11668,866],{"id":865},[842,11670,11671],{},"The signnow-mcp server exposes 11 tools that cover the full document lifecycle:",[871,11673,11674,11682],{},[874,11675,11676],{},[877,11677,11678,11680],{},[880,11679,882],{},[880,11681,885],{},[887,11683,11684,11694,11704,11714,11724,11734,11744,11754,11764,11774,11784],{},[877,11685,11686,11691],{},[892,11687,11688],{},[895,11689,11690],{},"upload_document",[892,11692,11693],{},"Upload a PDF to SignNow",[877,11695,11696,11701],{},[892,11697,11698],{},[895,11699,11700],{},"get_document",[892,11702,11703],{},"Get document details and signing status",[877,11705,11706,11711],{},[892,11707,11708],{},[895,11709,11710],{},"list_documents",[892,11712,11713],{},"List all documents in the account",[877,11715,11716,11721],{},[892,11717,11718],{},[895,11719,11720],{},"download_signed_document",[892,11722,11723],{},"Download a signed PDF locally",[877,11725,11726,11731],{},[892,11727,11728],{},[895,11729,11730],{},"send_signing_invite",[892,11732,11733],{},"Send a freeform e-signature invite",[877,11735,11736,11741],{},[892,11737,11738],{},[895,11739,11740],{},"send_role_based_invite",[892,11742,11743],{},"Send a role-based invite with field assignments",[877,11745,11746,11751],{},[892,11747,11748],{},[895,11749,11750],{},"cancel_invite",[892,11752,11753],{},"Cancel pending signing invites",[877,11755,11756,11761],{},[892,11757,11758],{},[895,11759,11760],{},"add_signature_field",[892,11762,11763],{},"Add a signature field to a document",[877,11765,11766,11771],{},[892,11767,11768],{},[895,11769,11770],{},"list_templates",[892,11772,11773],{},"List all document templates",[877,11775,11776,11781],{},[892,11777,11778],{},[895,11779,11780],{},"create_from_template",[892,11782,11783],{},"Create a document from a template",[877,11785,11786,11791],{},[892,11787,11788],{},[895,11789,11790],{},"register_webhook",[892,11792,11793],{},"Register a webhook for document events",[842,11795,11796],{},"Once connected, you interact with these tools through natural language. No API calls, no curl commands, no switching between browser tabs.",[863,11798,11800],{"id":11799},"how-it-looks-in-practice","How it looks in practice",[842,11802,11803],{},"Here's what it looks like when you ask Claude Code to list your SignNow documents:",[842,11805,11806],{},[1027,11807],{"alt":11808,"src":11809},"SignNow MCP in action — listing documents from Claude Code","https://raw.githubusercontent.com/musictechlab/signnow-mcp/main/docs/signnow-mcp-demo.webp",[842,11811,11812,11813,11815,11816,11820],{},"Claude calls the ",[895,11814,11710],{}," tool behind the scenes and presents the results in a clean table. You can then follow up with natural language — \"download the first one\", \"send document #2 to ",[846,11817,11819],{"href":11818},"mailto:john@example.com","john@example.com"," for signing\", or \"what's the status of the deposit confirmation?\".",[863,11822,1072],{"id":1071},[1074,11824,11826],{"id":11825},"_1-get-signnow-api-credentials","1. Get SignNow API credentials",[991,11828,11829,11838,11849,11858],{},[961,11830,11831,11832,11837],{},"Create a ",[846,11833,11836],{"href":11834,"rel":11835},"https://www.signnow.com/",[850],"SignNow"," account",[961,11839,11840,11841,11844,11845,11848],{},"Go to ",[996,11842,11843],{},"API"," > ",[996,11846,11847],{},"Applications"," and create an application",[961,11850,11851,11852,1589,11855],{},"Note your ",[895,11853,11854],{},"client_id",[895,11856,11857],{},"client_secret",[961,11859,11860],{},"Base64-encode them:",[1013,11862,11864],{"className":1080,"code":11863,"language":1082,"meta":728,"style":728},"echo -n \"client_id:client_secret\" | base64\n",[895,11865,11866],{"__ignoreMap":728},[1086,11867,11868,11871,11874,11876,11879,11881,11884],{"class":1088,"line":1089},[1086,11869,11870],{"class":1105},"echo",[1086,11872,11873],{"class":1096}," -n",[1086,11875,1195],{"class":1146},[1086,11877,11878],{"class":1096},"client_id:client_secret",[1086,11880,1159],{"class":1146},[1086,11882,11883],{"class":1146}," |",[1086,11885,11886],{"class":1092}," base64\n",[1074,11888,11890],{"id":11889},"_2-clone-and-install","2. Clone and install",[1013,11892,11894],{"className":1080,"code":11893,"language":1082,"meta":728,"style":728},"git clone https://github.com/musictechlab/signnow-mcp.git\ncd signnow-mcp\ncp .env.example .env\n# Edit .env with your credentials\npoetry install\n",[895,11895,11896,11905,11912,11923,11928],{"__ignoreMap":728},[1086,11897,11898,11900,11902],{"class":1088,"line":1089},[1086,11899,1093],{"class":1092},[1086,11901,1097],{"class":1096},[1086,11903,11904],{"class":1096}," https://github.com/musictechlab/signnow-mcp.git\n",[1086,11906,11907,11909],{"class":1088,"line":729},[1086,11908,1106],{"class":1105},[1086,11910,11911],{"class":1096}," signnow-mcp\n",[1086,11913,11914,11917,11920],{"class":1088,"line":1112},[1086,11915,11916],{"class":1092},"cp",[1086,11918,11919],{"class":1096}," .env.example",[1086,11921,11922],{"class":1096}," .env\n",[1086,11924,11925],{"class":1088,"line":1181},[1086,11926,11927],{"class":1427},"# Edit .env with your credentials\n",[1086,11929,11930,11932],{"class":1088,"line":1205},[1086,11931,1115],{"class":1092},[1086,11933,1118],{"class":1096},[1074,11935,11937],{"id":11936},"_3-register-with-claude-code","3. Register with Claude Code",[842,11939,11940],{},"The quickest way:",[1013,11942,11944],{"className":1080,"code":11943,"language":1082,"meta":728,"style":728},"claude mcp add signnow -- poetry -C /path/to/signnow-mcp run python -m signnow_mcp.server\n",[895,11945,11946],{"__ignoreMap":728},[1086,11947,11948,11950,11952,11954,11957,11959,11962,11965,11968,11971,11974,11977],{"class":1088,"line":1089},[1086,11949,6485],{"class":1092},[1086,11951,8553],{"class":1096},[1086,11953,8556],{"class":1096},[1086,11955,11956],{"class":1096}," signnow",[1086,11958,8568],{"class":1096},[1086,11960,11961],{"class":1096}," poetry",[1086,11963,11964],{"class":1096}," -C",[1086,11966,11967],{"class":1096}," /path/to/signnow-mcp",[1086,11969,11970],{"class":1096}," run",[1086,11972,11973],{"class":1096}," python",[1086,11975,11976],{"class":1096}," -m",[1086,11978,11979],{"class":1096}," signnow_mcp.server\n",[842,11981,11982],{},"Or add it manually to your MCP configuration:",[1013,11984,11986],{"className":1136,"code":11985,"filename":1138,"language":1139,"meta":728,"style":728},"{\n  \"signnow\": {\n    \"type\": \"stdio\",\n    \"command\": \"poetry\",\n    \"args\": [\"-C\", \"/path/to/signnow-mcp\", \"run\", \"python\", \"-m\", \"signnow_mcp.server\"],\n    \"env\": {\n      \"SIGNNOW_API_BASE_URL\": \"https://api.signnow.com\",\n      \"SIGNNOW_BASIC_AUTH\": \"your-base64-encoded-credentials\",\n      \"SIGNNOW_USERNAME\": \"your-email@example.com\",\n      \"SIGNNOW_PASSWORD\": \"your-password\"\n    }\n  }\n}\n",[895,11987,11988,11992,12005,12025,12043,12106,12119,12139,12159,12179,12197,12201,12205],{"__ignoreMap":728},[1086,11989,11990],{"class":1088,"line":1089},[1086,11991,1147],{"class":1146},[1086,11993,11994,11996,11999,12001,12003],{"class":1088,"line":729},[1086,11995,1152],{"class":1146},[1086,11997,11998],{"class":1155},"signnow",[1086,12000,1159],{"class":1146},[1086,12002,1133],{"class":1146},[1086,12004,1164],{"class":1146},[1086,12006,12007,12009,12012,12014,12016,12018,12021,12023],{"class":1088,"line":1112},[1086,12008,1169],{"class":1146},[1086,12010,12011],{"class":1092},"type",[1086,12013,1159],{"class":1146},[1086,12015,1133],{"class":1146},[1086,12017,1195],{"class":1146},[1086,12019,12020],{"class":1096},"stdio",[1086,12022,1159],{"class":1146},[1086,12024,1202],{"class":1146},[1086,12026,12027,12029,12031,12033,12035,12037,12039,12041],{"class":1088,"line":1181},[1086,12028,1169],{"class":1146},[1086,12030,1188],{"class":1092},[1086,12032,1159],{"class":1146},[1086,12034,1133],{"class":1146},[1086,12036,1195],{"class":1146},[1086,12038,1115],{"class":1096},[1086,12040,1159],{"class":1146},[1086,12042,1202],{"class":1146},[1086,12044,12045,12047,12049,12051,12053,12055,12057,12060,12062,12064,12066,12069,12071,12073,12075,12077,12079,12081,12083,12085,12087,12089,12091,12093,12095,12097,12099,12102,12104],{"class":1088,"line":1205},[1086,12046,1169],{"class":1146},[1086,12048,1210],{"class":1092},[1086,12050,1159],{"class":1146},[1086,12052,1133],{"class":1146},[1086,12054,1217],{"class":1146},[1086,12056,1159],{"class":1146},[1086,12058,12059],{"class":1096},"-C",[1086,12061,1159],{"class":1146},[1086,12063,1227],{"class":1146},[1086,12065,1195],{"class":1146},[1086,12067,12068],{"class":1096},"/path/to/signnow-mcp",[1086,12070,1159],{"class":1146},[1086,12072,1227],{"class":1146},[1086,12074,1195],{"class":1146},[1086,12076,1241],{"class":1096},[1086,12078,1159],{"class":1146},[1086,12080,1227],{"class":1146},[1086,12082,1195],{"class":1146},[1086,12084,1250],{"class":1096},[1086,12086,1159],{"class":1146},[1086,12088,1227],{"class":1146},[1086,12090,1195],{"class":1146},[1086,12092,1259],{"class":1096},[1086,12094,1159],{"class":1146},[1086,12096,1227],{"class":1146},[1086,12098,1195],{"class":1146},[1086,12100,12101],{"class":1096},"signnow_mcp.server",[1086,12103,1159],{"class":1146},[1086,12105,9297],{"class":1146},[1086,12107,12108,12110,12113,12115,12117],{"class":1088,"line":1276},[1086,12109,1169],{"class":1146},[1086,12111,12112],{"class":1092},"env",[1086,12114,1159],{"class":1146},[1086,12116,1133],{"class":1146},[1086,12118,1164],{"class":1146},[1086,12120,12121,12123,12126,12128,12130,12132,12135,12137],{"class":1088,"line":1282},[1086,12122,1184],{"class":1146},[1086,12124,12125],{"class":1187},"SIGNNOW_API_BASE_URL",[1086,12127,1159],{"class":1146},[1086,12129,1133],{"class":1146},[1086,12131,1195],{"class":1146},[1086,12133,12134],{"class":1096},"https://api.signnow.com",[1086,12136,1159],{"class":1146},[1086,12138,1202],{"class":1146},[1086,12140,12141,12143,12146,12148,12150,12152,12155,12157],{"class":1088,"line":1288},[1086,12142,1184],{"class":1146},[1086,12144,12145],{"class":1187},"SIGNNOW_BASIC_AUTH",[1086,12147,1159],{"class":1146},[1086,12149,1133],{"class":1146},[1086,12151,1195],{"class":1146},[1086,12153,12154],{"class":1096},"your-base64-encoded-credentials",[1086,12156,1159],{"class":1146},[1086,12158,1202],{"class":1146},[1086,12160,12161,12163,12166,12168,12170,12172,12175,12177],{"class":1088,"line":2685},[1086,12162,1184],{"class":1146},[1086,12164,12165],{"class":1187},"SIGNNOW_USERNAME",[1086,12167,1159],{"class":1146},[1086,12169,1133],{"class":1146},[1086,12171,1195],{"class":1146},[1086,12173,12174],{"class":1096},"your-email@example.com",[1086,12176,1159],{"class":1146},[1086,12178,1202],{"class":1146},[1086,12180,12181,12183,12186,12188,12190,12192,12195],{"class":1088,"line":2700},[1086,12182,1184],{"class":1146},[1086,12184,12185],{"class":1187},"SIGNNOW_PASSWORD",[1086,12187,1159],{"class":1146},[1086,12189,1133],{"class":1146},[1086,12191,1195],{"class":1146},[1086,12193,12194],{"class":1096},"your-password",[1086,12196,4441],{"class":1146},[1086,12198,12199],{"class":1088,"line":3398},[1086,12200,1279],{"class":1146},[1086,12202,12203],{"class":1088,"line":1715},[1086,12204,1285],{"class":1146},[1086,12206,12207],{"class":1088,"line":3409},[1086,12208,1291],{"class":1146},[1032,12210,12211],{},[842,12212,12213,12214,12217],{},"Use the sandbox environment (",[895,12215,12216],{},"https://api-eval.signnow.com",") for testing. SignNow provides 2,000 free signature invites in sandbox mode.",[1074,12219,12221],{"id":12220},"_4-start-using-it","4. Start using it",[842,12223,12224],{},"Once configured, just talk to Claude:",[958,12226,12227,12235,12240,12245,12250],{},[961,12228,12229],{},[964,12230,12231,12232,12234],{},"\"Upload contract.pdf to SignNow and send it to ",[846,12233,11819],{"href":11818}," for signing\"",[961,12236,12237],{},[964,12238,12239],{},"\"Check the signing status of document abc123\"",[961,12241,12242],{},[964,12243,12244],{},"\"List all my SignNow documents\"",[961,12246,12247],{},[964,12248,12249],{},"\"Download the signed version of the NDA\"",[961,12251,12252],{},[964,12253,12254],{},"\"Create a new document from the deposit template and send it to the client\"",[863,12256,12257],{"id":7532},"Why we built this",[842,12259,12260,12261,12266],{},"At MusicTech Lab, we use SignNow for sending contracts, NDAs, and deposit confirmations as part of our ",[846,12262,12265],{"href":12263,"rel":12264},"https://beatbuddy.pro",[850],"BeatBuddy"," onboarding flow. Our Django integration handles the automated pipeline, but there are always ad-hoc tasks — checking a document's status, resending an invite, downloading a signed copy for the records.",[842,12268,12269],{},"Before the MCP server, that meant logging into the SignNow dashboard or writing one-off API calls. Now it's a single sentence in Claude Code.",[1572,12271,12272],{},[842,12273,12274],{},"This pattern — wrapping a third-party API as an MCP server — works for any service with a REST API. If you find yourself repeatedly switching to a web dashboard to do something, consider building an MCP server for it.",[863,12276,12278],{"id":12277},"sandbox-vs-production","Sandbox vs. production",[842,12280,12281],{},"SignNow provides separate environments for testing and production:",[871,12283,12284,12297],{},[874,12285,12286],{},[877,12287,12288,12291,12294],{},[880,12289,12290],{},"Environment",[880,12292,12293],{},"API URL",[880,12295,12296],{},"App URL",[887,12298,12299,12313],{},[877,12300,12301,12304,12308],{},[892,12302,12303],{},"Sandbox",[892,12305,12306],{},[895,12307,12216],{},[892,12309,12310],{},[895,12311,12312],{},"https://app-eval.signnow.com",[877,12314,12315,12318,12322],{},[892,12316,12317],{},"Production",[892,12319,12320],{},[895,12321,12134],{},[892,12323,12324],{},[895,12325,12326],{},"https://app.signnow.com",[1901,12328,12329],{},[842,12330,12331],{},"Never use production credentials for development. The sandbox environment is free and gives you 2,000 signature invites to test with.",[863,12333,1642],{"id":1641},[958,12335,12336,12343,12348,12354,12361],{},[961,12337,12338,12342],{},[846,12339,12341],{"href":1586,"rel":12340},[850],"GitHub: musictechlab/signnow-mcp"," — the source code",[961,12344,12345,12347],{},[846,12346,542],{"href":543}," — our deep dive on the server-side integration",[961,12349,12350,12353],{},[846,12351,12352],{"href":165},"Querying Bandcamp Revenue with Natural Language"," — another MCP server we built, this time for music revenue analytics",[961,12355,12356,12360],{},[846,12357,12359],{"href":1675,"rel":12358},[850],"Model Context Protocol specification"," — the MCP standard",[961,12362,12363,12368],{},[846,12364,12367],{"href":12365,"rel":12366},"https://docs.signnow.com/",[850],"SignNow API documentation"," — official API reference",[1680,12370,12371],{},"html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":728,"searchDepth":729,"depth":729,"links":12373},[12374,12375,12376,12377,12383,12384,12385],{"id":11657,"depth":729,"text":8773},{"id":865,"depth":729,"text":866},{"id":11799,"depth":729,"text":11800},{"id":1071,"depth":729,"text":1072,"children":12378},[12379,12380,12381,12382],{"id":11825,"depth":1112,"text":11826},{"id":11889,"depth":1112,"text":11890},{"id":11936,"depth":1112,"text":11937},{"id":12220,"depth":1112,"text":12221},{"id":7532,"depth":729,"text":12257},{"id":12277,"depth":729,"text":12278},{"id":1641,"depth":729,"text":1642},"2026-03-12T00:00:00.000Z","We open-sourced an MCP server that brings SignNow e-signatures into Claude Code. Upload documents, send signing invites, track status, and download signed PDFs — all without leaving your terminal.",[12389,12392,12395,12398],{"question":12390,"answer":12391},"What is the SignNow MCP server?","It is an open-source Model Context Protocol server that connects airSlate SignNow's e-signature API to Claude Code and other MCP-compatible clients. It lets you manage documents, send signing invites, and download signed PDFs directly from your terminal.",{"question":12393,"answer":12394},"Is this an official SignNow product?","No. This is a community project built by MusicTech Lab. It is not affiliated with or endorsed by airSlate SignNow.",{"question":12396,"answer":12397},"What tools does the server provide?","The server exposes 11 tools: upload_document, get_document, list_documents, download_signed_document, send_signing_invite, send_role_based_invite, cancel_invite, add_signature_field, list_templates, create_from_template, and register_webhook.",{"question":1713,"answer":12399},"Yes. Any MCP-compatible client can use this server. Claude Code is the primary client we built it for, but the MCP protocol is open and supported by a growing number of tools.",{"src":12401,"credit":12402},"/images/blog/musictechlab_blog_signnow_mcp_server.webp","Photo by [Vitaly Gariev](https://unsplash.com/@silverkblack) on [Unsplash](https://unsplash.com/photos/iPheGw7_UaI)",{"enabled":738,"items":12404},[12405,12407,12410],{"text":12406,"icon":1723},"The signnow-mcp server exposes 11 tools covering the full document signing lifecycle.",{"text":12408,"icon":12409},"Natural language replaces API calls: upload, sign, and track documents from the terminal.","i-lucide-sparkles",{"text":12411,"icon":12412},"SignNow sandbox provides 2,000 free signature invites for development and testing.","i-lucide-package",{},{"title":12415,"description":12416},"SignNow MCP Server for Claude Code | MusicTech Lab","Open-source MCP server for SignNow e-signatures. Upload, sign, and manage documents from Claude Code or any MCP client.",[1379,1626,12418,11998,1736,12419,12420],"e-signatures","developer-tools","automation","rJ-D5aky3r9E1UyuToU6W-lLu8q7MVunwz7LPwgR0Y0",{"id":12423,"title":314,"authors":12424,"badge":12427,"body":12428,"category":756,"client":723,"date":12864,"description":12865,"extension":734,"faq":12866,"featured":738,"featuredOrder":1715,"hidden":69,"image":12876,"keyTakeaways":12878,"meta":12888,"navigation":738,"path":315,"seo":12889,"status":723,"stem":316,"tags":12892,"teaser":723,"__hash__":12899},"posts/blog/software-development/1200-looms-how-async-video-became-our-development-superpower.md",[12425],{"name":834,"to":720,"avatar":12426},{"src":722},{"label":5,"color":3237},{"type":725,"value":12429,"toc":12845},[12430,12441,12445,12448,12532,12542,12553,12556,12626,12629,12633,12636,12640,12643,12646,12666,12669,12674,12678,12681,12684,12688,12691,12694,12698,12701,12704,12708,12711,12715,12718,12724,12730,12736,12741,12745,12748,12752,12755,12759,12762,12766,12769,12773,12776,12780,12789,12793,12800,12826,12829,12831],[842,12431,12432,12433,12436,12437,12440],{},"I never planned to record over a thousand videos. In January 2021, I hit record on my first Loom — a quick demo for a client who couldn't make a call. Five years and ",[996,12434,12435],{},"1,212 videos"," later, it's become one of the most impactful habits I've built as a founder and developer. Over ",[996,12438,12439],{},"107 hours"," of recordings now document my demos, decisions, estimates, and code walkthroughs. Here's what I've learned.",[863,12442,12444],{"id":12443},"the-numbers-tell-a-story","The Numbers Tell a Story",[842,12446,12447],{},"I didn't set out to build an async video habit. It happened organically — and the growth curve surprised even me:",[871,12449,12450,12464],{},[874,12451,12452],{},[877,12453,12454,12457,12461],{},[880,12455,12456],{},"Year",[880,12458,12460],{"align":12459},"right","Videos Recorded",[880,12462,12463],{"align":12459},"Growth",[887,12465,12466,12477,12488,12499,12510,12521],{},[877,12467,12468,12471,12474],{},[892,12469,12470],{},"2021",[892,12472,12473],{"align":12459},"46",[892,12475,12476],{"align":12459},"—",[877,12478,12479,12482,12485],{},[892,12480,12481],{},"2022",[892,12483,12484],{"align":12459},"71",[892,12486,12487],{"align":12459},"+54%",[877,12489,12490,12493,12496],{},[892,12491,12492],{},"2023",[892,12494,12495],{"align":12459},"175",[892,12497,12498],{"align":12459},"+146%",[877,12500,12501,12504,12507],{},[892,12502,12503],{},"2024",[892,12505,12506],{"align":12459},"276",[892,12508,12509],{"align":12459},"+58%",[877,12511,12512,12515,12518],{},[892,12513,12514],{},"2025",[892,12516,12517],{"align":12459},"434",[892,12519,12520],{"align":12459},"+57%",[877,12522,12523,12526,12529],{},[892,12524,12525],{},"2026",[892,12527,12528],{"align":12459},"47 (Q1 so far)",[892,12530,12531],{"align":12459},"on pace for 188+",[842,12533,12534,12535,12538,12539,861],{},"What started as an occasional demo tool became a daily habit. My peak month — ",[996,12536,12537],{},"February 2025"," — saw 145 videos recorded. That's roughly ",[996,12540,12541],{},"7 Looms per working day",[842,12543,12544,12545,12548,12549,12552],{},"The average video is ",[996,12546,12547],{},"5 minutes and 30 seconds"," long — but the median is just ",[996,12550,12551],{},"1 minute 44 seconds",". That gap tells an important story: most of my Looms are quick, focused messages under 3 minutes. The longer client demos and sprint walkthroughs pull the average up. No meeting preamble, no \"can everyone hear me?\", no waiting for latecomers.",[842,12554,12555],{},"Here's the full distribution:",[871,12557,12558,12569],{},[874,12559,12560],{},[877,12561,12562,12564,12567],{},[880,12563,11305],{},[880,12565,12566],{"align":12459},"Videos",[880,12568,7938],{"align":12459},[887,12570,12571,12582,12593,12604,12615],{},[877,12572,12573,12576,12579],{},[892,12574,12575],{},"Under 1 min",[892,12577,12578],{"align":12459},"433",[892,12580,12581],{"align":12459},"36%",[877,12583,12584,12587,12590],{},[892,12585,12586],{},"1–3 min",[892,12588,12589],{"align":12459},"326",[892,12591,12592],{"align":12459},"27%",[877,12594,12595,12598,12601],{},[892,12596,12597],{},"3–7 min",[892,12599,12600],{"align":12459},"221",[892,12602,12603],{"align":12459},"18%",[877,12605,12606,12609,12612],{},[892,12607,12608],{},"7–10 min",[892,12610,12611],{"align":12459},"51",[892,12613,12614],{"align":12459},"4%",[877,12616,12617,12620,12623],{},[892,12618,12619],{},"Over 10 min",[892,12621,12622],{"align":12459},"140",[892,12624,12625],{"align":12459},"11%",[842,12627,12628],{},"Over a third of my recordings are under a minute — quick answers, visual pointers, \"here's what I mean\" moments. The majority (63%) are under 3 minutes. Only the deep demos and walkthroughs go longer.",[863,12630,12632],{"id":12631},"what-i-actually-use-loom-for","What I Actually Use Loom For",[842,12634,12635],{},"Looking at my library, clear patterns emerge across five years of recordings:",[1074,12637,12639],{"id":12638},"client-demos-and-pocs-80-videos","Client Demos and PoCs (80+ videos)",[842,12641,12642],{},"This is where async video delivers the most impact for me. When I finish a sprint or build a proof of concept, I record a walkthrough instead of scheduling a demo call. The client watches it when they have time, pauses to take notes, and rewatches sections they want to discuss.",[842,12644,12645],{},"A few real examples from my account:",[958,12647,12648,12654,12660],{},[961,12649,12650,12653],{},[964,12651,12652],{},"\"PoC SingBuddy\""," — walking a client through a new product concept before committing to a full sprint",[961,12655,12656,12659],{},[964,12657,12658],{},"\"Ambistream | Final demo for Sprint 5\""," — end-of-sprint delivery showing dynamic video layers in action",[961,12661,12662,12665],{},[964,12663,12664],{},"\"Amazon IVS Real-time Mobile Web Demo\""," — a technical PoC demonstrating live streaming capabilities for a music platform",[842,12667,12668],{},"The pattern is always the same: I open the app, walk through what I built, explain the decisions I made, and highlight what's next. Five minutes. Done.",[1032,12670,12671],{},[842,12672,12673],{},"Record your demo before the call, not instead of it. Send the video ahead so the live session focuses on questions, not presentation. This cuts meeting time by 50% or more.",[1074,12675,12677],{"id":12676},"estimates-and-financial-walkthroughs-119-videos","Estimates and Financial Walkthroughs (119 videos)",[842,12679,12680],{},"This one surprised me when I looked at the data. I've recorded 119 videos walking clients through spreadsheets — project estimates, cost breakdowns, timeline projections.",[842,12682,12683],{},"Spreadsheets are notoriously hard to communicate over email. A 3-minute Loom where I point at cells, explain trade-offs, and walk through assumptions eliminates 90% of follow-up questions. The client can pause, rewind, and share the video with their team. It's far more effective than a PDF attachment with a two-line email.",[1074,12685,12687],{"id":12686},"internal-tools-and-dashboard-updates-153-videos","Internal Tools and Dashboard Updates (153 videos)",[842,12689,12690],{},"My most recorded category — and the most underrated. Every internal tool I build, every dashboard update, every infrastructure change gets a quick Loom.",[842,12692,12693],{},"When a new team member joins MusicTech Lab and asks \"how does the deployment pipeline work?\" or \"how do I use the Ableton export tool?\", the answer is a 4-minute video, not a 30-minute onboarding session. These recordings aren't polished — they're raw, practical walkthroughs that serve as living documentation.",[1074,12695,12697],{"id":12696},"code-and-design-reviews-70-videos","Code and Design Reviews (70 videos)",[842,12699,12700],{},"I started recording code walkthroughs when I realized my PR comments were getting too long. Instead of leaving 20 inline comments, I record a 5-minute Loom explaining the architecture decision, the trade-offs I considered, and what reviewers should watch for.",[842,12702,12703],{},"The same applies to design reviews. When I receive a Figma mockup, I record my feedback as a video — pointing at specific elements, suggesting alternatives, asking questions. It's more nuanced than written comments and much faster to produce.",[1074,12705,12707],{"id":12706},"replacing-emails-24-videos","Replacing Emails (24 videos)",[842,12709,12710],{},"Some things are just faster to show than type. I've recorded 24 videos that directly replaced what would have been long email threads — quick screen recordings answering questions that would have taken 500 words to write but only 90 seconds to show.",[863,12712,12714],{"id":12713},"why-this-works-for-me-as-a-founder","Why This Works for Me as a Founder",[842,12716,12717],{},"Running MusicTech Lab means I'm constantly switching between roles — writing code, reviewing designs, talking to clients, planning sprints, handling finances. Async video fits into all of these contexts because it's fast to produce and respects other people's time.",[842,12719,12720,12723],{},[996,12721,12722],{},"Time zone independence."," Our clients span multiple time zones. A client in LA doesn't need to stay up for a demo with our team in Poland. They watch the Loom at 9 AM their time and leave comments. I respond the next morning. The work never stops, but nobody loses sleep.",[842,12725,12726,12729],{},[996,12727,12728],{},"Context preservation."," When I record my thought process, I create an artifact. Six months later, when someone asks \"why did we choose this architecture for the streaming module?\", the answer exists as a searchable video — not as a fading memory from a call nobody recorded.",[842,12731,12732,12735],{},[996,12733,12734],{},"Protecting deep work."," My most productive months — measured by commits and deployments — correlate with my highest Loom output. It sounds counterintuitive, but recording more videos means scheduling fewer meetings, which means more uninterrupted coding time.",[1572,12737,12738],{},[842,12739,12740],{},"I'm not anti-meeting. Some discussions need real-time collaboration — brainstorming sessions, difficult conversations, creative workshops. But the vast majority of \"status updates\" and \"quick demos\" are better served by a recorded video that respects everyone's calendar.",[863,12742,12744],{"id":12743},"what-id-tell-you-if-youre-starting","What I'd Tell You If You're Starting",[842,12746,12747],{},"After five years and 1,200+ recordings, here's what I'd recommend:",[1074,12749,12751],{"id":12750},"start-with-client-demos","Start with client demos",[842,12753,12754],{},"This is the highest-ROI use case. Record a 3-5 minute walkthrough of what you built, send it before the scheduled call. You'll immediately notice calls getting shorter and more productive. Clients love it — they can share the video with stakeholders who weren't on the original call.",[1074,12756,12758],{"id":12757},"make-it-a-reflex-not-a-process","Make it a reflex, not a process",[842,12760,12761],{},"I never made a rule about when to record. I just started hitting record whenever I caught myself typing a long explanation. Over time, it became second nature. The growth from 46 to 434 videos per year was entirely organic.",[1074,12763,12765],{"id":12764},"keep-it-under-2-minutes","Keep it under 2 minutes",[842,12767,12768],{},"My median is 1 minute 44 seconds. Over 60% of my recordings are under 3 minutes. If a Loom is getting longer than a few minutes, I split it. Short videos get watched; long videos get bookmarked and forgotten.",[1074,12770,12772],{"id":12771},"dont-polish","Don't polish",[842,12774,12775],{},"The biggest barrier to async video is perfectionism. My Looms have typos on screen, \"umm\"s in the audio, and cursors that occasionally wander off. Nobody has ever complained. The content matters, not the production quality.",[1074,12777,12779],{"id":12778},"build-a-searchable-archive","Build a searchable archive",[842,12781,12782,12783,1589,12786,861],{},"At 1,212 videos, finding a specific recording became a challenge. I recently built a custom tool (an MCP server using Claude Code) that connects to my Loom account and lets me search, analyze, and generate statistics from my entire video library. That's actually how I pulled the data for this article - every number here comes from querying my own recordings programmatically. We've built similar MCP integrations for other workflows too, like our ",[846,12784,12785],{"href":165},"Bandcamp revenue dashboard",[846,12787,12788],{"href":619},"SignNow e-signatures",[863,12790,12792],{"id":12791},"the-compounding-effect","The Compounding Effect",[842,12794,12795,12796,12799],{},"The real value of async video isn't in any single recording. It's in the ",[996,12797,12798],{},"compounding archive"," you build over time. My 1,212 videos are now:",[958,12801,12802,12808,12814,12820],{},[961,12803,12804,12807],{},[996,12805,12806],{},"An onboarding library"," — new team members watch relevant Looms instead of attending knowledge-transfer sessions",[961,12809,12810,12813],{},[996,12811,12812],{},"A decision log"," — architecture choices, business decisions, and trade-offs captured with full context",[961,12815,12816,12819],{},[996,12817,12818],{},"A portfolio"," — demos and PoCs I can reference when scoping similar projects",[961,12821,12822,12825],{},[996,12823,12824],{},"A content mine"," — this very article was built by analyzing my Loom data and reflecting on patterns I wouldn't have noticed without it",[842,12827,12828],{},"Every Loom I record today is a small investment in future efficiency. After 107 hours of recordings and over a thousand videos, I can say with confidence: async video isn't a productivity hack. For a developer running a remote-first agency, it's infrastructure.",[4937,12830],{},[842,12832,12833],{},[964,12834,12835,12836,12841,12842,861],{},"I'm Mariusz, founder of ",[846,12837,12840],{"href":12838,"rel":12839},"https://www.musictechlab.io",[850],"MusicTech Lab"," — a software development agency focused on the music industry. If you're curious about how we work or want to discuss a project, ",[846,12843,5405],{"href":4946,"rel":12844},[850],{"title":728,"searchDepth":729,"depth":729,"links":12846},[12847,12848,12855,12856,12863],{"id":12443,"depth":729,"text":12444},{"id":12631,"depth":729,"text":12632,"children":12849},[12850,12851,12852,12853,12854],{"id":12638,"depth":1112,"text":12639},{"id":12676,"depth":1112,"text":12677},{"id":12686,"depth":1112,"text":12687},{"id":12696,"depth":1112,"text":12697},{"id":12706,"depth":1112,"text":12707},{"id":12713,"depth":729,"text":12714},{"id":12743,"depth":729,"text":12744,"children":12857},[12858,12859,12860,12861,12862],{"id":12750,"depth":1112,"text":12751},{"id":12757,"depth":1112,"text":12758},{"id":12764,"depth":1112,"text":12765},{"id":12771,"depth":1112,"text":12772},{"id":12778,"depth":1112,"text":12779},{"id":12791,"depth":729,"text":12792},"2026-03-06T00:00:00.000Z","I've recorded 1,212 Loom videos over 5 years. Here's how async video transformed the way I demo, communicate with clients, and document decisions as a founder and developer.",[12867,12870,12873],{"question":12868,"answer":12869},"How does async video improve software development?","Async video replaces synchronous meetings with recorded walkthroughs that stakeholders can watch on their own time. It reduces scheduling overhead, creates a searchable archive of decisions, and lets developers communicate complex ideas visually without interrupting their flow.",{"question":12871,"answer":12872},"What types of development tasks work best with Loom?","Client demos, PoC walkthroughs, estimate explanations, design reviews, code walkthroughs, and bug reports. Essentially anything where showing your screen adds context that text alone cannot convey.",{"question":12874,"answer":12875},"How long should a development Loom video be?","Based on data across 1,212 videos, the median length is just 1 minute 44 seconds — most recordings are quick, focused messages. Longer demos average around 5-7 minutes. The key is matching length to purpose: quick answers under 2 minutes, demos and walkthroughs under 7.",{"src":12877},"/images/blog/musictechlab_blog_1200-looms-how-async-video-became-our-development-superpower.webp",{"enabled":738,"items":12879},[12880,12882,12884,12886],{"text":12881,"icon":3844},"1,212 Loom videos recorded over 5 years, totaling 107+ hours of async communication.",{"text":12883,"icon":3271},"Median video length is just 1 min 44 sec; 63% of recordings are under 3 minutes.",{"text":12885,"icon":2939},"Sending demos before calls cuts live meeting time by 50% or more.",{"text":12887,"icon":3920},"Peak usage hit 145 videos in one month, roughly 7 Looms per working day.",{},{"title":12890,"description":12891},"1,200 Looms: Async Video in Software Development | MusicTech Lab","How I recorded 1,212 Loom videos in 5 years to replace meetings, demo features, and build async-first culture. Practical lessons from a dev agency founder.",[12893,12894,12895,756,12896,12897,12898],"async-communication","loom","remote-work","client-demos","team-culture","productivity","xeQ-9jLoqIy12DSoIIL_CVDxlaBzg0GiG7VfUAgFZeA",{"id":12901,"title":80,"authors":12902,"badge":12905,"body":12906,"category":731,"client":723,"date":13540,"description":13541,"extension":734,"faq":13542,"featured":738,"featuredOrder":729,"hidden":69,"image":13552,"keyTakeaways":13554,"meta":13565,"navigation":738,"path":81,"seo":13566,"status":723,"stem":82,"tags":13569,"teaser":723,"__hash__":13570},"posts/blog/music-data/830-ways-to-say-spotify-normalizing-music-streaming-data.md",[12903],{"name":834,"to":720,"avatar":12904},{"src":722},{"label":5,"color":3237},{"type":725,"value":12907,"toc":13529},[12908,12915,12918,12922,12925,13012,13019,13023,13030,13137,13140,13146,13157,13172,13175,13179,13182,13185,13190,13196,13200,13203,13207,13210,13293,13296,13300,13303,13369,13372,13376,13379,13508,13511,13515,13518,13524,13527],[842,12909,12910,12911,12914],{},"In our ",[846,12912,12913],{"href":77},"previous article",", we showed what a real label's download folder looks like - 18 distributors, 5 file formats, 500+ files per year. But solving the file format problem is only step one.",[842,12916,12917],{},"Open those files. The data inside is just as messy.",[863,12919,12921],{"id":12920},"the-name-problem","The name problem",[842,12923,12924],{},"Ask 18 distributors what \"Spotify\" is called. You'll get more answers than you expect:",[871,12926,12927,12936],{},[874,12928,12929],{},[877,12930,12931,12933],{},[880,12932,10403],{},[880,12934,12935],{},"How they label Spotify",[887,12937,12938,12947,12957,12967,12977,12984,12993,13002],{},[877,12939,12940,12943],{},[892,12941,12942],{},"FUGA",[892,12944,12945],{},[895,12946,5070],{},[877,12948,12949,12952],{},[892,12950,12951],{},"ADA",[892,12953,12954],{},[895,12955,12956],{},"SPOTIFY",[877,12958,12959,12962],{},[892,12960,12961],{},"Ingrooves",[892,12963,12964],{},[895,12965,12966],{},"spotify",[877,12968,12969,12972],{},[892,12970,12971],{},"The Orchard",[892,12973,12974],{},[895,12975,12976],{},"Spotify Premium",[877,12978,12979,12981],{},[892,12980,8748],{},[892,12982,12983],{},"N/A",[877,12985,12986,12989],{},[892,12987,12988],{},"MVD",[892,12990,12991],{},[895,12992,5070],{},[877,12994,12995,12998],{},[892,12996,12997],{},"Emerald",[892,12999,13000],{},[895,13001,12956],{},[877,13003,13004,13007],{},[892,13005,13006],{},"SFM",[892,13008,13009],{},[895,13010,13011],{},"Spotify AB",[842,13013,13014,13015,13018],{},"That's just one platform. Now multiply across ",[996,13016,13017],{},"every retailer, every label name, and every service type"," in the dataset. The same entity appears under different names, different capitalizations, and different abbreviations depending on which distributor sent the file.",[863,13020,13022],{"id":13021},"_830-values-19-names","830 values, 19 names",[842,13024,13025,13026,13029],{},"The solution is what we call ",[996,13027,13028],{},"unions"," - normalization groups that map many raw values to one canonical name.",[1045,13031,13035],{"className":13032},[13033,13034,1052],"flex","justify-center",[1013,13036,13038],{"className":3341,"code":13037,"language":3343,"meta":728,"style":728},"flowchart LR\n    subgraph raw[\"Raw Values\"]\n        A[\"Spotify\"]\n        B[\"SPOTIFY\"]\n        C[\"spotify\"]\n        D[\"Spotify Premium\"]\n        E[\"Spotify AB\"]\n        F[\"Spotify Ltd\"]\n    end\n\n    subgraph union[\"Union\"]\n        G([\"SPOTIFY\"])\n    end\n\n    A --> G\n    B --> G\n    C --> G\n    D --> G\n    E --> G\n    F --> G\n",[895,13039,13040,13045,13050,13055,13060,13065,13070,13075,13080,13085,13089,13094,13099,13103,13107,13112,13117,13122,13127,13132],{"__ignoreMap":728},[1086,13041,13042],{"class":1088,"line":1089},[1086,13043,13044],{"class":1436},"flowchart LR\n",[1086,13046,13047],{"class":1088,"line":729},[1086,13048,13049],{"class":1436},"    subgraph raw[\"Raw Values\"]\n",[1086,13051,13052],{"class":1088,"line":1112},[1086,13053,13054],{"class":1436},"        A[\"Spotify\"]\n",[1086,13056,13057],{"class":1088,"line":1181},[1086,13058,13059],{"class":1436},"        B[\"SPOTIFY\"]\n",[1086,13061,13062],{"class":1088,"line":1205},[1086,13063,13064],{"class":1436},"        C[\"spotify\"]\n",[1086,13066,13067],{"class":1088,"line":1276},[1086,13068,13069],{"class":1436},"        D[\"Spotify Premium\"]\n",[1086,13071,13072],{"class":1088,"line":1282},[1086,13073,13074],{"class":1436},"        E[\"Spotify AB\"]\n",[1086,13076,13077],{"class":1088,"line":1288},[1086,13078,13079],{"class":1436},"        F[\"Spotify Ltd\"]\n",[1086,13081,13082],{"class":1088,"line":2685},[1086,13083,13084],{"class":1436},"    end\n",[1086,13086,13087],{"class":1088,"line":2700},[1086,13088,3390],{"emptyLinePlaceholder":738},[1086,13090,13091],{"class":1088,"line":3398},[1086,13092,13093],{"class":1436},"    subgraph union[\"Union\"]\n",[1086,13095,13096],{"class":1088,"line":1715},[1086,13097,13098],{"class":1436},"        G([\"SPOTIFY\"])\n",[1086,13100,13101],{"class":1088,"line":3409},[1086,13102,13084],{"class":1436},[1086,13104,13105],{"class":1088,"line":3415},[1086,13106,3390],{"emptyLinePlaceholder":738},[1086,13108,13109],{"class":1088,"line":3421},[1086,13110,13111],{"class":1436},"    A --> G\n",[1086,13113,13114],{"class":1088,"line":3427},[1086,13115,13116],{"class":1436},"    B --> G\n",[1086,13118,13119],{"class":1088,"line":3433},[1086,13120,13121],{"class":1436},"    C --> G\n",[1086,13123,13124],{"class":1088,"line":3439},[1086,13125,13126],{"class":1436},"    D --> G\n",[1086,13128,13129],{"class":1088,"line":3444},[1086,13130,13131],{"class":1436},"    E --> G\n",[1086,13133,13134],{"class":1088,"line":3450},[1086,13135,13136],{"class":1436},"    F --> G\n",[842,13138,13139],{},"Here's what the union management interface looks like in practice - TIDAL alone has 10 variations, SoundCloud over 30:",[842,13141,13142],{},[1027,13143],{"alt":13144,"src":13145},"Union management interface showing raw value mappings","/images/blog/musictechlab_blog_unions-admin-interface.webp",[842,13147,13148,13149,13152,13153,13156],{},"In production, this system maps ",[996,13150,13151],{},"830 raw tag values"," to just ",[996,13154,13155],{},"19 unions"," across three dimensions:",[1045,13158,13160,13164,13168],{"className":13159},[1048,1049,1050,1051,1052],[1054,13161],{"description":13162,"title":13163},"Spotify, Amazon, YouTube, Pandora, Facebook, and more. Each with dozens of naming variations across sources.","Retailer unions",[1054,13165],{"description":13166,"title":13167},"The label itself can appear under different names, abbreviations, or legal entity variations across distributors.","Label unions",[1054,13169],{"description":13170,"title":13171},"Streaming, downloads, physical, sync - each distributor categorizes their services differently.","Service unions",[842,13173,13174],{},"When a file is imported, every raw value is checked against the union mappings. If it matches, the canonical name is stored alongside the original. If it doesn't, the raw value is preserved - and flagged for review.",[863,13176,13178],{"id":13177},"why-not-just-find-and-replace","Why not just find-and-replace?",[842,13180,13181],{},"Because new values appear constantly. A distributor adds a sub-brand. Another changes their internal naming. A third introduces a typo that persists for six months before anyone notices. Static find-and-replace breaks every time the data evolves.",[842,13183,13184],{},"Unions are dynamic. A non-technical user can add a new mapping through an admin interface - no code changes, no redeployment. The next import picks it up automatically.",[1572,13186,13187],{},[842,13188,13189],{},"One real example: a distributor renamed their Spotify column from \"Spotify\" to \"Spotify AB\" mid-year. Without union mapping, this would have created a second \"Spotify\" in every report, splitting the data and making quarterly comparisons unreliable.",[842,13191,13192],{},[1027,13193],{"alt":13194,"src":13195},"Currency exchange and data normalization","/images/blog/musictechlab_blog_currency-exchange-data.webp",[863,13197,13199],{"id":13198},"the-cherry-on-top-currencies-and-territories","The cherry on top: currencies and territories",[842,13201,13202],{},"Even after normalizing names, two more problems remain.",[1074,13204,13206],{"id":13205},"currencies","Currencies",[842,13208,13209],{},"Every distributor reports income in their own currency. And they don't even agree on what to call the currency column:",[871,13211,13212,13224],{},[874,13213,13214],{},[877,13215,13216,13218,13221],{},[880,13217,10403],{},[880,13219,13220],{},"Column name",[880,13222,13223],{},"Default currency",[887,13225,13226,13237,13247,13258,13270,13282],{},[877,13227,13228,13230,13235],{},[892,13229,12942],{},[892,13231,13232],{},[895,13233,13234],{},"Original currency",[892,13236,5087],{},[877,13238,13239,13241,13244],{},[892,13240,12951],{},[892,13242,13243],{},"(file-level)",[892,13245,13246],{},"GBP",[877,13248,13249,13251,13256],{},[892,13250,12961],{},[892,13252,13253],{},[895,13254,13255],{},"CURRENCY_CODE",[892,13257,13246],{},[877,13259,13260,13262,13267],{},[892,13261,12971],{},[892,13263,13264],{},[895,13265,13266],{},"Preferred Currency",[892,13268,13269],{},"USD",[877,13271,13272,13275,13280],{},[892,13273,13274],{},"Merlin",[892,13276,13277],{},[895,13278,13279],{},"Payable currency",[892,13281,5087],{},[877,13283,13284,13286,13291],{},[892,13285,12988],{},[892,13287,13288],{},[895,13289,13290],{},"currency-code",[892,13292,5087],{},[842,13294,13295],{},"To compare income across sources, every value needs to be converted to a single target currency using exchange rates tied to specific dates. A $1,000 Orchard payment and a £800 ADA payment aren't comparable until you normalize them.",[1074,13297,13299],{"id":13298},"territories","Territories",[842,13301,13302],{},"Countries seem straightforward - until you see how distributors label them:",[871,13304,13305,13315],{},[874,13306,13307],{},[877,13308,13309,13312],{},[880,13310,13311],{},"The same country",[880,13313,13314],{},"Variations across sources",[887,13316,13317,13335,13353],{},[877,13318,13319,13322],{},[892,13320,13321],{},"United States",[892,13323,13324,5660,13327,5660,13330,5660,13333],{},[895,13325,13326],{},"US",[895,13328,13329],{},"USA",[895,13331,13332],{},"UNITED STATES",[895,13334,13321],{},[877,13336,13337,13340],{},[892,13338,13339],{},"United Kingdom",[892,13341,13342,5660,13345,5660,13348,5660,13351],{},[895,13343,13344],{},"UK",[895,13346,13347],{},"GB",[895,13349,13350],{},"UNITED KINGDOM (GB)",[895,13352,13339],{},[877,13354,13355,13358],{},[892,13356,13357],{},"Czech Republic",[892,13359,13360,5660,13363,5660,13366],{},[895,13361,13362],{},"CZ",[895,13364,13365],{},"CZECH REPUBLIC",[895,13367,13368],{},"Czechia",[842,13370,13371],{},"Each variation needs to map to a standard two-letter code. Without this, \"show me all US streams\" misses half the data.",[863,13373,13375],{"id":13374},"three-layers-of-normalization","Three layers of normalization",[842,13377,13378],{},"Here's the full picture - what it actually takes to turn raw distributor data into something usable:",[1045,13380,13382],{"className":13381},[13033,13034,1052],[1013,13383,13385],{"className":3341,"code":13384,"language":3343,"meta":728,"style":728},"flowchart TD\n    subgraph layer1[\"Layer 1: File Formats\"]\n        A[\"18 adapters\"]\n        B[\"5 formats\"]\n        C[\"500+ files/year\"]\n    end\n\n    subgraph layer2[\"Layer 2: Name Normalization\"]\n        D[\"830 raw values\"]\n        E[\"19 unions\"]\n        F[\"3 dimensions\"]\n    end\n\n    subgraph layer3[\"Layer 3: Currency & Territory\"]\n        G[\"Multiple currencies\"]\n        H[\"Exchange rates\"]\n        I[\"Country code mapping\"]\n    end\n\n    subgraph result[\"Result\"]\n        J[(\"One clean dataset\")]\n    end\n\n    layer1 --> layer2\n    layer2 --> layer3\n    layer3 --> result\n",[895,13386,13387,13391,13396,13401,13406,13411,13415,13419,13424,13429,13434,13439,13443,13447,13452,13457,13462,13467,13471,13475,13480,13485,13489,13493,13498,13503],{"__ignoreMap":728},[1086,13388,13389],{"class":1088,"line":1089},[1086,13390,3350],{"class":1436},[1086,13392,13393],{"class":1088,"line":729},[1086,13394,13395],{"class":1436},"    subgraph layer1[\"Layer 1: File Formats\"]\n",[1086,13397,13398],{"class":1088,"line":1112},[1086,13399,13400],{"class":1436},"        A[\"18 adapters\"]\n",[1086,13402,13403],{"class":1088,"line":1181},[1086,13404,13405],{"class":1436},"        B[\"5 formats\"]\n",[1086,13407,13408],{"class":1088,"line":1205},[1086,13409,13410],{"class":1436},"        C[\"500+ files/year\"]\n",[1086,13412,13413],{"class":1088,"line":1276},[1086,13414,13084],{"class":1436},[1086,13416,13417],{"class":1088,"line":1282},[1086,13418,3390],{"emptyLinePlaceholder":738},[1086,13420,13421],{"class":1088,"line":1288},[1086,13422,13423],{"class":1436},"    subgraph layer2[\"Layer 2: Name Normalization\"]\n",[1086,13425,13426],{"class":1088,"line":2685},[1086,13427,13428],{"class":1436},"        D[\"830 raw values\"]\n",[1086,13430,13431],{"class":1088,"line":2700},[1086,13432,13433],{"class":1436},"        E[\"19 unions\"]\n",[1086,13435,13436],{"class":1088,"line":3398},[1086,13437,13438],{"class":1436},"        F[\"3 dimensions\"]\n",[1086,13440,13441],{"class":1088,"line":1715},[1086,13442,13084],{"class":1436},[1086,13444,13445],{"class":1088,"line":3409},[1086,13446,3390],{"emptyLinePlaceholder":738},[1086,13448,13449],{"class":1088,"line":3415},[1086,13450,13451],{"class":1436},"    subgraph layer3[\"Layer 3: Currency & Territory\"]\n",[1086,13453,13454],{"class":1088,"line":3421},[1086,13455,13456],{"class":1436},"        G[\"Multiple currencies\"]\n",[1086,13458,13459],{"class":1088,"line":3427},[1086,13460,13461],{"class":1436},"        H[\"Exchange rates\"]\n",[1086,13463,13464],{"class":1088,"line":3433},[1086,13465,13466],{"class":1436},"        I[\"Country code mapping\"]\n",[1086,13468,13469],{"class":1088,"line":3439},[1086,13470,13084],{"class":1436},[1086,13472,13473],{"class":1088,"line":3444},[1086,13474,3390],{"emptyLinePlaceholder":738},[1086,13476,13477],{"class":1088,"line":3450},[1086,13478,13479],{"class":1436},"    subgraph result[\"Result\"]\n",[1086,13481,13482],{"class":1088,"line":3456},[1086,13483,13484],{"class":1436},"        J[(\"One clean dataset\")]\n",[1086,13486,13487],{"class":1088,"line":3462},[1086,13488,13084],{"class":1436},[1086,13490,13491],{"class":1088,"line":3467},[1086,13492,3390],{"emptyLinePlaceholder":738},[1086,13494,13495],{"class":1088,"line":3473},[1086,13496,13497],{"class":1436},"    layer1 --> layer2\n",[1086,13499,13500],{"class":1088,"line":3479},[1086,13501,13502],{"class":1436},"    layer2 --> layer3\n",[1086,13504,13505],{"class":1088,"line":3485},[1086,13506,13507],{"class":1436},"    layer3 --> result\n",[842,13509,13510],{},"Most teams get stuck at Layer 1 and never even reach the name and currency problems. But without all three layers, you can't answer basic questions like \"What were our total Spotify streams in Q3, in GBP?\"",[863,13512,13514],{"id":13513},"the-payoff","The payoff",[842,13516,13517],{},"After all three normalization layers, that question becomes trivial. Filter by retailer union, filter by date range, and every value is already in GBP. One query. One answer. No spreadsheet wrangling.",[842,13519,13520,13521,13523],{},"And once the data is clean, even non-technical users can get answers. We built an ",[846,13522,7567],{"href":85}," that lets anyone type a question in plain English and get a chart back in seconds, no SQL required.",[842,13525,13526],{},"That's the difference between a collection of files and a data platform. We build the latter.",[1680,13528,3806],{},{"title":728,"searchDepth":729,"depth":729,"links":13530},[13531,13532,13533,13534,13538,13539],{"id":12920,"depth":729,"text":12921},{"id":13021,"depth":729,"text":13022},{"id":13177,"depth":729,"text":13178},{"id":13198,"depth":729,"text":13199,"children":13535},[13536,13537],{"id":13205,"depth":1112,"text":13206},{"id":13298,"depth":1112,"text":13299},{"id":13374,"depth":729,"text":13375},{"id":13513,"depth":729,"text":13514},"2026-03-03T00:00:00.000Z","After solving the file format problem, the data inside is just as messy. Different names for the same platforms, labels, currencies, and territories. Here's how we normalize it.",[13543,13546,13549],{"question":13544,"answer":13545},"Why is music streaming data so inconsistent?","Every distributor uses their own naming conventions for retailers, labels, services, territories, and currencies. There is no shared standard, so the same platform can appear under dozens of different names across data sources.",{"question":13547,"answer":13548},"What is a data union in the context of music royalties?","A union is a normalization group that maps multiple raw values to one canonical name. For example, all variations of 'Spotify' across 18 distributors get mapped to a single SPOTIFY union, making cross-source analysis possible.",{"question":13550,"answer":13551},"How do currency differences affect music royalty reporting?","Distributors report income in different currencies (USD, GBP, EUR) and even label the currency column differently. To compare or aggregate income across sources, every value must be converted to a single target currency using exchange rates.",{"src":13553},"/images/blog/musictechlab_blog_830-ways-to-say-spotify.webp",{"enabled":738,"items":13555},[13556,13558,13560,13563],{"text":13557,"icon":2895},"830 raw data values map to just 19 canonical names across retailers, labels, and services.",{"text":13559,"icon":1769},"Three normalization layers are needed: file formats, name unions, and currency/territory mapping.",{"text":13561,"icon":13562},"Unions are dynamic and admin-editable, so new naming variations need no code changes.","i-lucide-settings",{"text":13564,"icon":3847},"Without all three layers, even basic queries like total Spotify streams in Q3 are impossible.",{},{"title":13567,"description":13568},"Normalizing Music Streaming Data: 830 Values, 19 Unions | MusicTech Lab","How we normalize 830 raw data values into 19 canonical names across retailers, labels, and services. Real-world music data normalization.",[731,5523,5519,3857],"nkhKea-amz1NLgquVd-h3Qvo8Tq2jJzXtEx0Fmt3TeE",{"id":13572,"title":338,"authors":13573,"badge":13576,"body":13577,"category":756,"client":723,"date":14068,"description":14069,"extension":734,"faq":14070,"featured":69,"featuredOrder":723,"hidden":69,"image":14083,"keyTakeaways":14085,"meta":14095,"navigation":738,"path":339,"seo":14096,"status":723,"stem":340,"tags":14099,"teaser":723,"__hash__":14106},"posts/blog/software-development/ai-audio-similarity-search-for-sound-libraries.md",[13574],{"name":719,"to":720,"avatar":13575},{"src":722},{"label":5,"color":3237},{"type":725,"value":13578,"toc":14047},[13579,13582,13592,13595,13599,13602,13619,13622,13626,13629,13632,13665,13669,13672,13676,13679,13692,13698,13702,13705,13714,13719,13723,13726,13735,13740,13745,13749,13752,13823,13839,13844,13848,13851,13903,13906,13912,13916,13919,13923,13926,13930,13933,13937,13940,13944,13947,13951,13954,13994,13998,14001,14006,14012,14018,14024,14026,14029,14032,14035,14045],[842,13580,13581],{},"If you manage a sound effects library with thousands of files, you already know the problem: a client needs \"a subtle metallic scrape, almost like a blade on glass,\" and your search bar returns nothing useful. The tags say \"metal,\" \"scrape,\" \"impact\" - but none of those capture the specific texture they need.",[842,13583,13584,13585,13588,13589,861],{},"This is where AI audio similarity search changes the game. Instead of relying on how someone ",[964,13586,13587],{},"described"," a sound, it analyzes what the sound actually ",[964,13590,13591],{},"sounds like",[842,13593,13594],{},"We have been researching this problem as part of our work in music technology, where sound libraries with thousands of short, similar-sounding effects are common. Traditional metadata simply cannot capture the nuances between a \"sharp metallic ping\" and a \"bright metallic tap.\" Here is what we have found about the available approaches, their trade-offs, and what works in production.",[863,13596,13598],{"id":13597},"the-problem-with-tags","The Problem with Tags",[842,13600,13601],{},"Before diving into solutions, it is worth understanding why traditional search breaks down for sound libraries.",[1045,13603,13605,13610,13614],{"className":13604},[1048,1049,1050,1051,1052],[1054,13606],{"description":13607,"icon":13608,"title":13609},"Different people tag the same sound differently. One person's 'whoosh' is another's 'swish.'","i-lucide-tag","Inconsistent Tagging",[1054,13611],{"description":13612,"icon":3271,"title":13613},"Manually tagging thousands of SFX is expensive and never complete. New sounds need immediate categorization.","Time-Consuming",[1054,13615],{"description":13616,"icon":13617,"title":13618},"Tags capture categories, not textures. 'Explosion' doesn't tell you if it's a deep rumble or a sharp crack.","i-lucide-ear","Nuance Gets Lost",[842,13620,13621],{},"For long, distinct audio files like full songs, tags work reasonably well. But for short sound effects (often just 1-3 seconds) where dozens of files live in the same category, tags cannot express the subtle differences that matter to a sound designer picking the perfect effect for a scene.",[863,13623,13625],{"id":13624},"how-ai-audio-search-works","How AI Audio Search Works",[842,13627,13628],{},"The core idea is simple: convert each sound into a mathematical representation (called an \"embedding\") that captures its acoustic properties, then use vector math to find similar sounds.",[842,13630,13631],{},"Here is the process in three steps:",[1045,13633,13635,13643,13650],{"className":13634},[1048,1049,1050,1051,1052],[1054,13636,13640],{"description":13637,"icon":13638,"title":13639},"AI model listens to each new SFX and generates a 512-number vector - a fingerprint of what the sound 'sounds like.'","i-lucide-upload","Step 1: Analyze on Upload",[842,13641,13642],{},"When a new file is uploaded, the AI model processes the audio and produces a numerical embedding that captures its acoustic characteristics: pitch, texture, rhythm, decay. Think of it as a fingerprint, but for how the sound is perceived rather than its waveform shape.",[1054,13644,13647],{"description":13645,"icon":2895,"title":13646},"Vectors are stored alongside metadata in a vector database for lightning-fast similarity search.","Step 2: Store Embeddings",[842,13648,13649],{},"These vectors live next to the regular metadata (title, tags, duration) in a specialized vector database. This enables similarity calculations across millions of sounds in milliseconds, not minutes.",[1054,13651,13654],{"description":13652,"icon":7560,"title":13653},"Users search by clicking 'find similar' or typing a natural language description.","Step 3: Search by Sound",[842,13655,13656,13657,13660,13661,13664],{},"Two powerful search modes become available. ",[996,13658,13659],{},"\"Find similar\"",": click a button on any sound, and acoustically similar results surface instantly. ",[996,13662,13663],{},"Natural language",": type \"subtle glass clink with reverb\" and the AI matches your words against actual audio content.",[863,13666,13668],{"id":13667},"available-methods-what-are-the-options","Available Methods: What Are the Options?",[842,13670,13671],{},"Not all AI audio search is created equal. Here are the main approaches, ranked from simplest to most powerful.",[1074,13673,13675],{"id":13674},"metadata-based-similarity-no-ai","Metadata-Based Similarity (No AI)",[842,13677,13678],{},"The simplest approach: find sounds with overlapping tags, the same category, and similar duration. No machine learning required.",[1045,13680,13682,13687],{"className":13681},[1048,1049,1765,1051,1052],[1054,13683],{"description":13684,"icon":13685,"title":13686},"Easy to implement, no ML infrastructure needed, fast and predictable.","i-lucide-check","Pros",[1054,13688],{"description":13689,"icon":13690,"title":13691},"Only as good as your tags. Cannot find acoustically similar sounds with different metadata.","i-lucide-x","Cons",[842,13693,13694,13697],{},[996,13695,13696],{},"Best for:"," Small libraries (under 1,000 files) with consistent, thorough tagging.",[1074,13699,13701],{"id":13700},"panns-pre-trained-audio-neural-networks","PANNs (Pre-trained Audio Neural Networks)",[842,13703,13704],{},"PANNs are deep learning models trained on AudioSet (Google's dataset of 2M+ labeled audio clips). They can classify sounds into 527 categories and produce embeddings that capture acoustic properties.",[1045,13706,13708,13711],{"className":13707},[1048,1049,1765,1051,1052],[1054,13709],{"description":13710,"icon":13685,"title":13686},"Well-established, strong classification accuracy, good embeddings for similarity search.",[1054,13712],{"description":13713,"icon":13690,"title":13691},"No text-to-audio search. Classification only, so you still need a separate system for natural language queries.",[842,13715,13716,13718],{},[996,13717,13696],{}," Libraries that need audio-to-audio similarity but do not need natural language search.",[1074,13720,13722],{"id":13721},"clap-contrastive-language-audio-pretraining","CLAP (Contrastive Language-Audio Pretraining)",[842,13724,13725],{},"CLAP is the breakthrough model for sound library search. Developed by Microsoft and LAION, it understands both text and audio in the same vector space. This means a text description and an audio file can be directly compared mathematically.",[1045,13727,13729,13732],{"className":13728},[1048,1049,1765,1051,1052],[1054,13730],{"description":13731,"icon":13685,"title":13686},"Text-to-audio AND audio-to-audio search. Natural language queries work out of the box. State-of-the-art accuracy.",[1054,13733],{"description":13734,"icon":13690,"title":13691},"Larger model (requires GPU for efficient batch processing). Newer, so less community tooling than PANNs.",[842,13736,13737,13739],{},[996,13738,13696],{}," Professional sound libraries where natural language search and acoustic similarity are both critical.",[1032,13741,13742],{},[842,13743,13744],{},"CLAP is worth serious consideration for sound library projects. The ability to search by typing \"distant thunder with light rain\" and getting acoustically relevant results - not just tag matches - could be a significant UX advantage over traditional approaches.",[863,13746,13748],{"id":13747},"the-technical-stack-for-the-curious","The Technical Stack (For the Curious)",[842,13750,13751],{},"If you are evaluating this for your own project, here is the architecture we recommend:",[1013,13753,13755],{"className":3341,"code":13754,"language":3343,"meta":728,"style":728},"flowchart LR\n    subgraph Indexing[\"Indexing Pipeline\"]\n        A[Audio Upload] --> B[CLAP Model]\n        B --> C[512-dim Vector]\n        C --> D[(Vector Database)]\n    end\n\n    subgraph Search[\"Search Pipeline\"]\n        E[User Query\\ntext or audio] --> F[CLAP Model]\n        F --> G[Query Vector]\n        G --> H{Nearest Neighbor\\nSearch}\n        D --> H\n        H --> I[Ranked Results]\n    end\n",[895,13756,13757,13761,13766,13771,13776,13781,13785,13789,13794,13799,13804,13809,13814,13819],{"__ignoreMap":728},[1086,13758,13759],{"class":1088,"line":1089},[1086,13760,13044],{"class":1436},[1086,13762,13763],{"class":1088,"line":729},[1086,13764,13765],{"class":1436},"    subgraph Indexing[\"Indexing Pipeline\"]\n",[1086,13767,13768],{"class":1088,"line":1112},[1086,13769,13770],{"class":1436},"        A[Audio Upload] --> B[CLAP Model]\n",[1086,13772,13773],{"class":1088,"line":1181},[1086,13774,13775],{"class":1436},"        B --> C[512-dim Vector]\n",[1086,13777,13778],{"class":1088,"line":1205},[1086,13779,13780],{"class":1436},"        C --> D[(Vector Database)]\n",[1086,13782,13783],{"class":1088,"line":1276},[1086,13784,13084],{"class":1436},[1086,13786,13787],{"class":1088,"line":1282},[1086,13788,3390],{"emptyLinePlaceholder":738},[1086,13790,13791],{"class":1088,"line":1288},[1086,13792,13793],{"class":1436},"    subgraph Search[\"Search Pipeline\"]\n",[1086,13795,13796],{"class":1088,"line":2685},[1086,13797,13798],{"class":1436},"        E[User Query\\ntext or audio] --> F[CLAP Model]\n",[1086,13800,13801],{"class":1088,"line":2700},[1086,13802,13803],{"class":1436},"        F --> G[Query Vector]\n",[1086,13805,13806],{"class":1088,"line":3398},[1086,13807,13808],{"class":1436},"        G --> H{Nearest Neighbor\\nSearch}\n",[1086,13810,13811],{"class":1088,"line":1715},[1086,13812,13813],{"class":1436},"        D --> H\n",[1086,13815,13816],{"class":1088,"line":3409},[1086,13817,13818],{"class":1436},"        H --> I[Ranked Results]\n",[1086,13820,13821],{"class":1088,"line":3415},[1086,13822,13084],{"class":1436},[1045,13824,13826,13830,13834],{"className":13825},[1048,1049,1050,1051,1052],[1054,13827],{"description":13828,"icon":11614,"title":13829},"LAION-AI/CLAP generates embeddings for both audio and text in a shared vector space.","CLAP Model",[1054,13831],{"description":13832,"icon":2895,"title":13833},"pgvector (PostgreSQL), Qdrant, or Pinecone for storing and querying embeddings at scale.","Vector Database",[1054,13835],{"description":13836,"icon":13837,"title":13838},"Pre-compute embeddings on upload (batch job), never at query time. Users never wait.","i-lucide-cog","Processing Pipeline",[1572,13840,13841],{},[842,13842,13843],{},"We prefer pgvector when the project already uses PostgreSQL (e.g., via Supabase). It keeps the infrastructure simple - no separate vector database to manage. For libraries over 1M files, a dedicated solution like Qdrant or Pinecone offers better performance.",[1074,13845,13847],{"id":13846},"performance-numbers","Performance Numbers",[842,13849,13850],{},"From our benchmarks with a 10,000-file SFX library:",[871,13852,13853,13861],{},[874,13854,13855],{},[877,13856,13857,13859],{},[880,13858,7621],{},[880,13860,7844],{},[887,13862,13863,13871,13879,13887,13895],{},[877,13864,13865,13868],{},[892,13866,13867],{},"Embedding generation",[892,13869,13870],{},"~200ms per file (GPU), ~2s per file (CPU)",[877,13872,13873,13876],{},[892,13874,13875],{},"Similarity search (pgvector)",[892,13877,13878],{},"\u003C 50ms for top-20 results",[877,13880,13881,13884],{},[892,13882,13883],{},"Natural language search",[892,13885,13886],{},"\u003C 100ms (text encoding + vector search)",[877,13888,13889,13892],{},[892,13890,13891],{},"Storage overhead",[892,13893,13894],{},"~2KB per sound (512-dim float32 vector)",[877,13896,13897,13900],{},[892,13898,13899],{},"Initial indexing (10K files)",[892,13901,13902],{},"~30 minutes (GPU)",[842,13904,13905],{},"For a 10,000-file library, the total vector storage is about 20MB - negligible compared to the audio files themselves.",[842,13907,13908],{},[1027,13909],{"alt":13910,"src":13911},"AI audio similarity search transforms how sound designers discover the right SFX","/images/blog/musictechlab_blog_ai-audio-similarity-search-for-sound-libraries_inline_1.webp",[863,13913,13915],{"id":13914},"business-impact-why-this-matters","Business Impact: Why This Matters",[842,13917,13918],{},"Beyond the technical elegance, AI audio search delivers measurable business value:",[1074,13920,13922],{"id":13921},"faster-client-workflows","Faster client workflows",[842,13924,13925],{},"Sound designers spend less time browsing and more time creating. When a client can type \"heavy door slam, wooden, no echo\" and get five perfect matches in under a second, that is time saved on every project.",[1074,13927,13929],{"id":13928},"better-discovery-of-existing-assets","Better discovery of existing assets",[842,13931,13932],{},"Most sound libraries have a \"long tail\" problem - hundreds of sounds that rarely get used because nobody remembers they exist or cannot find them through tags. Similarity search surfaces these forgotten assets, increasing the value of the entire library.",[1074,13934,13936],{"id":13935},"reduced-tagging-overhead","Reduced tagging overhead",[842,13938,13939],{},"While tags are still useful for broad categorization, the pressure to tag every sound with exhaustive detail drops significantly. The AI fills in the gaps that human tagging misses.",[1074,13941,13943],{"id":13942},"competitive-differentiation","Competitive differentiation",[842,13945,13946],{},"For studios offering sound libraries to clients, AI-powered search is still uncommon. Offering \"describe what you need and find it instantly\" is a compelling feature that sets a library apart from competitors still using basic keyword search.",[863,13948,13950],{"id":13949},"what-this-looks-like-in-practice","What This Looks Like in Practice",[842,13952,13953],{},"Imagine a film editor working on a trailer. They need a very specific sound: something between a metallic ring and a glass chime, with a quick decay. Here is how the workflow changes:",[1045,13955,13957,13976],{"className":13956},[1048,1049,1765,1051,1052],[1054,13958,13962],{"description":13959,"icon":13960,"title":13961},"15+ minutes, settling for 'close enough'","i-lucide-search-x","Without AI Search",[991,13963,13964,13967,13970,13973],{},[961,13965,13966],{},"Search \"metal\" - 200 results, mostly impacts and scrapes",[961,13968,13969],{},"Search \"glass\" - 150 results, mostly breaks and shatters",[961,13971,13972],{},"Search \"chime\" - 30 results, browse through each one",[961,13974,13975],{},"Give up after 15 minutes and settle for \"close enough\"",[1054,13977,13980],{"description":13978,"icon":12409,"title":13979},"Under 2 minutes, the perfect sound","With AI Search",[991,13981,13982,13985,13988,13991],{},[961,13983,13984],{},"Type \"metallic ring with glass chime quality, short decay\"",[961,13986,13987],{},"Get 10 acoustically relevant results in under a second",[961,13989,13990],{},"Click \"find similar\" on the closest match to refine further",[961,13992,13993],{},"Download the perfect sound in under 2 minutes",[863,13995,13997],{"id":13996},"limitations-and-honest-trade-offs","Limitations and Honest Trade-offs",[842,13999,14000],{},"No technology is perfect. Here is what to keep in mind:",[1901,14002,14003],{},[842,14004,14005],{},"AI similarity search works best as a complement to traditional search, not a replacement. Tags and categories still provide the structural navigation that users need for browsing. AI search excels at the \"I know what I want but cannot describe it in keywords\" use case.",[842,14007,14008,14011],{},[996,14009,14010],{},"Model accuracy varies by domain."," CLAP was trained on general audio data. For highly specialized libraries (e.g., only foley sounds, only synthesizer patches), fine-tuning the model on your specific data can improve results significantly - but adds development time.",[842,14013,14014,14017],{},[996,14015,14016],{},"Initial setup requires processing power."," Generating embeddings for a large existing library is a one-time batch job, but it does require GPU access. Cloud GPUs (AWS, GCP) make this affordable - expect around $5-20 for processing 10,000 files.",[842,14019,14020,14023],{},[996,14021,14022],{},"Relevance is subjective."," \"Similar\" means different things to different people. A sound designer might consider two sounds similar because of their texture, while another focuses on rhythm or pitch. The AI captures overall acoustic similarity, which is usually - but not always - what users want.",[863,14025,8506],{"id":3083},[842,14027,14028],{},"If you are considering AI audio search for your sound library, here is our recommended approach:",[3572,14030],{":items":14031},"[{\"title\":\"Start with CLAP Embeddings\",\"description\":\"Get both text-to-audio and audio-to-audio search from the very beginning. One model, two search modes.\",\"icon\":\"i-lucide-brain\"},{\"title\":\"Use pgvector on PostgreSQL\",\"description\":\"If you are already on PostgreSQL, add the pgvector extension. Avoid infrastructure complexity early on.\",\"icon\":\"i-lucide-database\"},{\"title\":\"Pre-compute on Upload\",\"description\":\"Generate embeddings when sounds are uploaded, not when users search. Never make users wait for real-time analysis.\",\"icon\":\"i-lucide-cog\"},{\"title\":\"Keep Traditional Search Alongside AI\",\"description\":\"Let users choose between keyword filtering and natural language search. Both have their place.\",\"icon\":\"i-lucide-layers\"},{\"title\":\"Collect Usage Data\",\"description\":\"Track which AI results users actually download. Use this signal to measure and improve relevance over time.\",\"icon\":\"i-lucide-bar-chart\"}]",[842,14033,14034],{},"The technology is mature enough for production use today, and the user experience improvement is dramatic. For sound libraries where traditional search falls short - especially collections of short, similar-sounding effects - AI similarity search is not a nice-to-have. It is the feature that makes the library actually usable.",[1572,14036,14037],{},[842,14038,14039,14042,14043,861],{},[996,14040,14041],{},"Related reading:"," If you are interested in how AI can also transform data analytics in the music industry, check out our article on ",[846,14044,84],{"href":85},[1680,14046,3806],{},{"title":728,"searchDepth":729,"depth":729,"links":14048},[14049,14050,14051,14056,14059,14065,14066,14067],{"id":13597,"depth":729,"text":13598},{"id":13624,"depth":729,"text":13625},{"id":13667,"depth":729,"text":13668,"children":14052},[14053,14054,14055],{"id":13674,"depth":1112,"text":13675},{"id":13700,"depth":1112,"text":13701},{"id":13721,"depth":1112,"text":13722},{"id":13747,"depth":729,"text":13748,"children":14057},[14058],{"id":13846,"depth":1112,"text":13847},{"id":13914,"depth":729,"text":13915,"children":14060},[14061,14062,14063,14064],{"id":13921,"depth":1112,"text":13922},{"id":13928,"depth":1112,"text":13929},{"id":13935,"depth":1112,"text":13936},{"id":13942,"depth":1112,"text":13943},{"id":13949,"depth":729,"text":13950},{"id":13996,"depth":729,"text":13997},{"id":3083,"depth":729,"text":8506},"2026-03-01T00:00:00.000Z","How AI-powered audio search is replacing tags and keywords, helping sound designers find the right SFX in seconds instead of minutes.",[14071,14074,14077,14080],{"question":14072,"answer":14073},"What is AI audio similarity search?","It's a technology that analyzes the actual sound content of audio files and finds acoustically similar sounds, even when tags or metadata don't match. Instead of searching by keywords, users can search by describing what a sound 'sounds like' or clicking 'find similar' on any sound.",{"question":14075,"answer":14076},"How is this different from tag-based search?","Traditional search relies on human-assigned tags, which are inconsistent, incomplete, and subjective. AI audio search analyzes the acoustic properties of each sound, so it can find similar sounds even when they were tagged differently by different people.",{"question":14078,"answer":14079},"What is CLAP and how does it work?","CLAP (Contrastive Language-Audio Pretraining) is an AI model that understands both text and audio. It converts sounds into mathematical vectors, enabling both text-to-audio search (describe what you want) and audio-to-audio similarity (find sounds like this one).",{"question":14081,"answer":14082},"How long does it take to implement AI audio search?","A basic implementation with pre-computed embeddings and vector search can be built in 2-4 weeks. The main effort is in the initial embedding pipeline and search tuning, not in ongoing maintenance.",{"src":14084},"/images/blog/musictechlab_blog_ai-audio-similarity-search-for-sound-libraries.webp",{"enabled":738,"items":14086},[14087,14089,14091,14093],{"text":14088,"icon":11614},"AI audio search finds sounds by acoustic similarity, not just tags or keywords.",{"text":14090,"icon":8500},"CLAP embeddings convert each sound into a 512-number vector fingerprint.",{"text":14092,"icon":3271},"A basic implementation with vector search can be built in 2 to 4 weeks.",{"text":14094,"icon":9547},"Tags fail for short SFX; dozens of 1-3 second files share the same category.",{},{"title":14097,"description":14098},"AI Audio Similarity Search for Sound Libraries | MusicTech Lab","Learn how CLAP embeddings and vector search help sound designers find SFX by acoustic similarity, not just tags. Business and technical guide.",[14100,14101,14102,14103,14104,14105,764],"AI","audio-search","sound-design","CLAP","vector-search","SFX","GWSVyrLINooVhaaAhJTIVykDqeysmp7wmXzOwCj9Td0",{"id":14108,"title":76,"authors":14109,"badge":14112,"body":14113,"category":731,"client":723,"date":14776,"description":14777,"extension":734,"faq":14778,"featured":738,"featuredOrder":1089,"hidden":69,"image":14788,"keyTakeaways":14790,"meta":14800,"navigation":738,"path":77,"seo":14801,"status":723,"stem":78,"tags":14804,"teaser":723,"__hash__":14805},"posts/blog/music-data/13-distributors-5-file-formats-zero-standards-the-reality-of-music-royalty-data.md",[14110],{"name":834,"to":720,"avatar":14111},{"src":722},{"label":1745,"color":1746},{"type":725,"value":14114,"toc":14768},[14115,14118,14121,14125,14128,14409,14416,14420,14423,14442,14445,14449,14452,14455,14466,14471,14477,14481,14484,14582,14585,14589,14592,14738,14741,14752,14759,14763,14766],[842,14116,14117],{},"Every month, an independent label receives royalty reports from over a dozen distributors. Not a single one looks the same.",[842,14119,14120],{},"This isn't a hypothetical. This is what a real download folder looks like when you work with music royalty data at scale.",[863,14122,14124],{"id":14123},"the-wall-of-files","The wall of files",[842,14126,14127],{},"Here's a small sample of actual filenames from a single label's monthly data intake -anonymised, but otherwise untouched:",[871,14129,14130,14145],{},[874,14131,14132],{},[877,14133,14134,14136,14139,14142],{},[880,14135,10403],{},[880,14137,14138],{},"Example Filename",[880,14140,14141],{},"Format",[880,14143,14144],{},"File Size",[887,14146,14147,14162,14177,14192,14207,14221,14235,14248,14262,14277,14293,14308,14323,14338,14352,14366,14380,14395],{},[877,14148,14149,14151,14156,14159],{},[892,14150,12942],{},[892,14152,14153],{},[895,14154,14155],{},"FUGA_Statement_June_2024.xlsx",[892,14157,14158],{},".xlsx",[892,14160,14161],{},"~1 MB",[877,14163,14164,14166,14171,14174],{},[892,14165,12951],{},[892,14167,14168],{},[895,14169,14170],{},"SR1_Distribution_Aug_24_-_00054061_-_2024-8.xlsb",[892,14172,14173],{},".xlsb",[892,14175,14176],{},"~70 KB",[877,14178,14179,14181,14186,14189],{},[892,14180,12961],{},[892,14182,14183],{},[895,14184,14185],{},"20240801-1496-DS-GBP_Digital_Sales.csv",[892,14187,14188],{},".csv",[892,14190,14191],{},"up to 226 MB",[877,14193,14194,14196,14201,14204],{},[892,14195,12971],{},[892,14197,14198],{},[895,14199,14200],{},"The_Orchard20240821_Jun2024_fullreport_catalogue_US.xls",[892,14202,14203],{},".xls",[892,14205,14206],{},"up to 700 MB",[877,14208,14209,14211,14216,14218],{},[892,14210,8748],{},[892,14212,14213],{},[895,14214,14215],{},"bandcamp_rev_report_20240801-20240831.csv",[892,14217,14188],{},[892,14219,14220],{},"~6 KB",[877,14222,14223,14225,14230,14232],{},[892,14224,12988],{},[892,14226,14227],{},[895,14228,14229],{},"MVD_Statement_DigitalSales_2024-07.xls",[892,14231,14203],{},[892,14233,14234],{},"~12 KB",[877,14236,14237,14239,14244,14246],{},[892,14238,12988],{},[892,14240,14241],{},[895,14242,14243],{},"MVD_Statement_DigitalSales_2024-07.xlsx",[892,14245,14158],{},[892,14247,14234],{},[877,14249,14250,14252,14257,14259],{},[892,14251,12997],{},[892,14253,14254],{},[895,14255,14256],{},"Emerald_202408_DSR.csv",[892,14258,14188],{},[892,14260,14261],{},"~46 MB",[877,14263,14264,14267,14272,14274],{},[892,14265,14266],{},"Safari Records",[892,14268,14269],{},[895,14270,14271],{},"Safari_Records_202408_DSR.xlsx",[892,14273,14158],{},[892,14275,14276],{},"~2 MB",[877,14278,14279,14282,14287,14290],{},[892,14280,14281],{},"ADA (legacy)",[892,14283,14284],{},[895,14285,14286],{},"ADAOCT1.XLS",[892,14288,14289],{},".XLS",[892,14291,14292],{},"up to 150 MB",[877,14294,14295,14298,14303,14305],{},[892,14296,14297],{},"MAC",[892,14299,14300],{},[895,14301,14302],{},"MAC_Developments_iTunes_August_2024.xlsx",[892,14304,14158],{},[892,14306,14307],{},"~49 KB",[877,14309,14310,14313,14318,14321],{},[892,14311,14312],{},"Absolute",[892,14314,14315],{},[895,14316,14317],{},"Absolute_2024021.CSV",[892,14319,14320],{},".CSV",[892,14322,14191],{},[877,14324,14325,14328,14333,14335],{},[892,14326,14327],{},"Qello",[892,14329,14330],{},[895,14331,14332],{},"DetailedSheet_Records_Ltd_20240801_20240831.xlsx",[892,14334,14158],{},[892,14336,14337],{},"~10 KB",[877,14339,14340,14342,14347,14349],{},[892,14341,13006],{},[892,14343,14344],{},[895,14345,14346],{},"sfmaug2024.xlsx",[892,14348,14158],{},[892,14350,14351],{},"~2.5 MB",[877,14353,14354,14357,14362,14364],{},[892,14355,14356],{},"BOFM",[892,14358,14359],{},[895,14360,14361],{},"BOFM_Aug2024.xlsx",[892,14363,14158],{},[892,14365,14351],{},[877,14367,14368,14371,14376,14378],{},[892,14369,14370],{},"Dome Records",[892,14372,14373],{},[895,14374,14375],{},"Dome_Records_202408_DSR.csv",[892,14377,14188],{},[892,14379,14161],{},[877,14381,14382,14385,14390,14392],{},[892,14383,14384],{},"MDR",[892,14386,14387],{},[895,14388,14389],{},"MDR_May-2024_65634.92_Records.xlsx",[892,14391,14158],{},[892,14393,14394],{},"~500 KB",[877,14396,14397,14399,14404,14406],{},[892,14398,13274],{},[892,14400,14401],{},[895,14402,14403],{},"Merlin_Nov24_eg.for.jack.xlsx",[892,14405,14158],{},[892,14407,14408],{},"~703 KB",[842,14410,14411,14412,14415],{},"That's ",[996,14413,14414],{},"18 adapters across 500+ files per year"," -each with its own naming convention, file format, and internal structure. From a 6 KB Bandcamp CSV to a single Orchard report that can reach 700 MB.",[863,14417,14419],{"id":14418},"spot-the-pattern","Spot the pattern",[842,14421,14422],{},"Go ahead, try. You won't find one.",[1045,14424,14426,14430,14434,14438],{"className":14425},[1048,1049,1765,1051,1052],[1054,14427],{"description":14428,"title":14429},"`.xlsx` · `.xlsb` · `.xls` · `.XLS` · `.csv` · `.CSV`","5 file formats",[1054,14431],{"description":14432,"title":14433},"`2024-07` · `202408` · `Aug_24` · `August_2024` · `20240801-20240831` · `2024021`","6 date conventions in filenames",[1054,14435],{"description":14436,"title":14437},"Some distributors send both `.xls` and `.xlsx` versions of the exact same data.","Same report, multiple formats",[1054,14439],{"description":14440,"title":14441},"camelCase, ALLCAPS, underscores, hyphens, internal reference numbers, random hash suffixes.","No naming standard",[842,14443,14444],{},"And that's just the filenames. Open these files and you'll find different column names for the same data, different date formats inside the cells, different encodings, and multi-sheet workbooks where each sheet follows its own rules.",[863,14446,14448],{"id":14447},"why-this-matters","Why this matters",[842,14450,14451],{},"Someone has to make sense of all this. Every month.",[842,14453,14454],{},"For most independent labels, that means hours of manual work -copying data between spreadsheets, reformatting dates, matching column names, fixing encoding issues that turn artist names into garbled text.",[842,14456,14457,14458,14461,14462,14465],{},"The cost isn't just time. It's ",[996,14459,14460],{},"delayed royalty payments"," to artists. It's ",[996,14463,14464],{},"reporting errors"," that erode trust. It's the finance team spending their week on data cleanup instead of analysis.",[1572,14467,14468],{},[842,14469,14470],{},"One distributor changed their report format mid-year without notice. The same filename pattern, but completely different column structure inside. Manual processes break silently when this happens.",[842,14472,14473],{},[1027,14474],{"alt":14475,"src":14476},"Someone working on a laptop with spreadsheet data","/images/blog/musictechlab_blog_royalty-data-spreadsheet-laptop.webp",[863,14478,14480],{"id":14479},"how-teams-try-to-solve-this","How teams try to solve this",[842,14482,14483],{},"There's more than one way to tackle this problem. Here's how the most common approaches compare:",[871,14485,14486,14505],{},[874,14487,14488],{},[877,14489,14490,14493,14496,14499,14502],{},[880,14491,14492],{},"Approach",[880,14494,14495],{},"Setup effort",[880,14497,14498],{},"Maintenance",[880,14500,14501],{},"Handles format changes",[880,14503,14504],{},"Scales with new sources",[887,14506,14507,14526,14544,14563],{},[877,14508,14509,14514,14517,14520,14523],{},[892,14510,14511],{},[996,14512,14513],{},"Manual spreadsheets",[892,14515,14516],{},"None",[892,14518,14519],{},"Hours every month",[892,14521,14522],{},"Breaks silently",[892,14524,14525],{},"Every new source = more hours",[877,14527,14528,14534,14536,14538,14541],{},[892,14529,14530,14533],{},[996,14531,14532],{},"Generic ETL tools"," (Fivetran, Airbyte)",[892,14535,2126],{},[892,14537,2123],{},[892,14539,14540],{},"Limited - connectors are generic",[892,14542,14543],{},"Only if a connector exists",[877,14545,14546,14551,14554,14557,14560],{},[892,14547,14548],{},[996,14549,14550],{},"Custom Python scripts",[892,14552,14553],{},"High",[892,14555,14556],{},"High - fragile, hard to maintain",[892,14558,14559],{},"Depends on the developer",[892,14561,14562],{},"Every new source = new script",[877,14564,14565,14570,14573,14576,14579],{},[892,14566,14567],{},[996,14568,14569],{},"Adapter-based pipeline",[892,14571,14572],{},"High upfront",[892,14574,14575],{},"Low - each adapter is isolated",[892,14577,14578],{},"Adapter update, no side effects",[892,14580,14581],{},"Add an adapter, done",[842,14583,14584],{},"Generic ETL tools work well for standardised APIs and databases. But music royalty data doesn't come from APIs - it comes from email attachments, FTP servers, and distributor portals. Each source is its own special case. That's why an adapter-based approach wins here: each distributor gets its own parser, isolated from the rest, easy to update when formats change.",[863,14586,14588],{"id":14587},"one-clean-dataset","One clean dataset",[842,14590,14591],{},"Here's what the pipeline looks like in practice:",[1013,14593,14595],{"className":3341,"code":14594,"language":3343,"meta":728,"style":728},"flowchart LR\n    subgraph sources[\"Raw Files\"]\n        A[\".xlsx\"]\n        B[\".xlsb\"]\n        C[\".xls\"]\n        D[\".csv\"]\n    end\n\n    subgraph adapters[\"Adapter Layer\"]\n        E[\"FUGA\"]\n        F[\"ADA\"]\n        G[\"Orchard\"]\n        H[\"Bandcamp\"]\n        I[\"+ 14 more\"]\n    end\n\n    subgraph output[\"Unified Output\"]\n        J[(\"Clean dataset\")]\n    end\n\n    A --> E\n    B --> F\n    C --> G\n    D --> H\n\n    E --> J\n    F --> J\n    G --> J\n    H --> J\n    I --> J\n",[895,14596,14597,14601,14606,14611,14616,14621,14626,14630,14634,14639,14644,14649,14654,14659,14664,14668,14672,14677,14682,14686,14690,14695,14700,14704,14709,14713,14718,14723,14728,14733],{"__ignoreMap":728},[1086,14598,14599],{"class":1088,"line":1089},[1086,14600,13044],{"class":1436},[1086,14602,14603],{"class":1088,"line":729},[1086,14604,14605],{"class":1436},"    subgraph sources[\"Raw Files\"]\n",[1086,14607,14608],{"class":1088,"line":1112},[1086,14609,14610],{"class":1436},"        A[\".xlsx\"]\n",[1086,14612,14613],{"class":1088,"line":1181},[1086,14614,14615],{"class":1436},"        B[\".xlsb\"]\n",[1086,14617,14618],{"class":1088,"line":1205},[1086,14619,14620],{"class":1436},"        C[\".xls\"]\n",[1086,14622,14623],{"class":1088,"line":1276},[1086,14624,14625],{"class":1436},"        D[\".csv\"]\n",[1086,14627,14628],{"class":1088,"line":1282},[1086,14629,13084],{"class":1436},[1086,14631,14632],{"class":1088,"line":1288},[1086,14633,3390],{"emptyLinePlaceholder":738},[1086,14635,14636],{"class":1088,"line":2685},[1086,14637,14638],{"class":1436},"    subgraph adapters[\"Adapter Layer\"]\n",[1086,14640,14641],{"class":1088,"line":2700},[1086,14642,14643],{"class":1436},"        E[\"FUGA\"]\n",[1086,14645,14646],{"class":1088,"line":3398},[1086,14647,14648],{"class":1436},"        F[\"ADA\"]\n",[1086,14650,14651],{"class":1088,"line":1715},[1086,14652,14653],{"class":1436},"        G[\"Orchard\"]\n",[1086,14655,14656],{"class":1088,"line":3409},[1086,14657,14658],{"class":1436},"        H[\"Bandcamp\"]\n",[1086,14660,14661],{"class":1088,"line":3415},[1086,14662,14663],{"class":1436},"        I[\"+ 14 more\"]\n",[1086,14665,14666],{"class":1088,"line":3421},[1086,14667,13084],{"class":1436},[1086,14669,14670],{"class":1088,"line":3427},[1086,14671,3390],{"emptyLinePlaceholder":738},[1086,14673,14674],{"class":1088,"line":3433},[1086,14675,14676],{"class":1436},"    subgraph output[\"Unified Output\"]\n",[1086,14678,14679],{"class":1088,"line":3439},[1086,14680,14681],{"class":1436},"        J[(\"Clean dataset\")]\n",[1086,14683,14684],{"class":1088,"line":3444},[1086,14685,13084],{"class":1436},[1086,14687,14688],{"class":1088,"line":3450},[1086,14689,3390],{"emptyLinePlaceholder":738},[1086,14691,14692],{"class":1088,"line":3456},[1086,14693,14694],{"class":1436},"    A --> E\n",[1086,14696,14697],{"class":1088,"line":3462},[1086,14698,14699],{"class":1436},"    B --> F\n",[1086,14701,14702],{"class":1088,"line":3467},[1086,14703,13121],{"class":1436},[1086,14705,14706],{"class":1088,"line":3473},[1086,14707,14708],{"class":1436},"    D --> H\n",[1086,14710,14711],{"class":1088,"line":3479},[1086,14712,3390],{"emptyLinePlaceholder":738},[1086,14714,14715],{"class":1088,"line":3485},[1086,14716,14717],{"class":1436},"    E --> J\n",[1086,14719,14720],{"class":1088,"line":3491},[1086,14721,14722],{"class":1436},"    F --> J\n",[1086,14724,14725],{"class":1088,"line":3497},[1086,14726,14727],{"class":1436},"    G --> J\n",[1086,14729,14730],{"class":1088,"line":3503},[1086,14731,14732],{"class":1436},"    H --> J\n",[1086,14734,14735],{"class":1088,"line":3509},[1086,14736,14737],{"class":1436},"    I --> J\n",[842,14739,14740],{},"Every file goes through its format-specific adapter - handling encoding, column mapping, date parsing, and multi-sheet logic. What comes out the other side is one consistent dataset: same columns, same date format, same encoding. Ready for analysis, reporting, and artist payouts.",[842,14742,14743,14744,14747,14748,14751],{},"But consistent columns are only the beginning. The data inside those files is ",[846,14745,14746],{"href":81},"just as messy"," - 830 raw values that need mapping to 19 canonical names before you can run a single meaningful query. And once the data is truly clean, it opens the door to ",[846,14749,14750],{"href":85},"AI-powered analytics"," where business users ask questions in plain English and get charts back in seconds.",[842,14753,14754,14755,14758],{},"That's what we build at MusicTech Lab. Not another dashboard on top of messy data - but the ",[996,14756,14757],{},"data layer underneath"," that turns chaos into clarity.",[863,14760,14762],{"id":14761},"looks-familiar","Looks familiar?",[842,14764,14765],{},"If your monthly royalty workflow involves more spreadsheet wrangling than actual analysis, we should talk. We've built data pipelines for independent labels handling exactly this kind of complexity - and we can do the same for you.",[1680,14767,3806],{},{"title":728,"searchDepth":729,"depth":729,"links":14769},[14770,14771,14772,14773,14774,14775],{"id":14123,"depth":729,"text":14124},{"id":14418,"depth":729,"text":14419},{"id":14447,"depth":729,"text":14448},{"id":14479,"depth":729,"text":14480},{"id":14587,"depth":729,"text":14588},{"id":14761,"depth":729,"text":14762},"2026-02-27T00:00:00.000Z","Every month, independent labels receive royalty reports from over a dozen distributors. No two look the same. Here's what that actually looks like.",[14779,14782,14785],{"question":14780,"answer":14781},"Why do music distributors use different file formats?","There is no industry-wide standard for royalty report delivery. Each distributor built their own reporting system independently, resulting in different file formats, column names, date conventions, and encodings.",{"question":14783,"answer":14784},"What file formats are used in music royalty reporting?","Common formats include XLSX (modern Excel), XLSB (binary Excel), XLS (legacy Excel), and CSV with various encodings (UTF-8, UTF-16, ISO-8859-1). Some distributors send the same report in multiple formats.",{"question":14786,"answer":14787},"How can labels automate royalty data processing?","By building format-aware adapters that recognise each distributor's file structure and automatically normalise everything into a single, clean dataset -eliminating manual cleanup and reducing errors.",{"src":14789},"/images/blog/musictechlab_blog_13-distributors-5-file-formats-zero-standards.webp",{"enabled":738,"items":14791},[14792,14794,14796,14798],{"text":14793,"icon":8737},"18 adapters parse 500+ files per year from 13 distributors in 5 different formats.",{"text":14795,"icon":3850},"File sizes range from 6 KB (Bandcamp) to 700 MB (The Orchard) with zero naming standards.",{"text":14797,"icon":1769},"Adapter-based pipelines isolate each source, making format changes safe and side-effect-free.",{"text":14799,"icon":3847},"Manual spreadsheet processing delays royalty payments and introduces silent reporting errors.",{},{"title":14802,"description":14803},"Music Royalty Data Chaos: 13 Distributors, 5 Formats | MusicTech Lab","See the real complexity of music royalty reporting -different file formats, naming conventions, and schemas from every distributor. Learn how automation solves it.",[731,5523,3192,5519,3857],"RALDAG90DAM9IgwQPuVrQkFWVhTDlAAddo9XQWvnld4",{"id":14807,"title":160,"authors":14808,"badge":14811,"body":14812,"category":731,"client":723,"date":15250,"description":15251,"extension":734,"faq":15252,"featured":738,"featuredOrder":1112,"hidden":69,"image":15262,"keyTakeaways":15264,"meta":15274,"navigation":738,"path":161,"seo":15275,"status":723,"stem":162,"tags":15278,"teaser":723,"__hash__":15280},"posts/blog/music-data/maintaining-music-tech-tools-the-sla-dilemma-for-small-teams.md",[14809],{"name":834,"to":720,"avatar":14810},{"src":722},{"label":5,"color":3237},{"type":725,"value":14813,"toc":15236},[14814,14817,14820,14823,14827,14830,14833,14836,14841,14845,14848,14851,14908,14913,14917,14920,14992,14998,15001,15007,15011,15018,15023,15026,15043,15046,15050,15053,15056,15084,15087,15093,15097,15101,15112,15117,15121,15127,15130,15134,15137,15142,15146,15149,15216,15220,15226,15229,15231],[842,14815,14816],{},"We built a streaming analytics platform for an independent European label. It aggregated royalty records from multiple distributors into a single searchable dashboard. The label loved it. Their head of operations said it \"completely changed how we handle reporting.\" The tool was stable, fast, and exactly what they needed.",[842,14818,14819],{},"And then nobody maintained it.",[842,14821,14822],{},"This is a story about what happens next, and what we learned about pricing maintenance for niche music tech tools.",[863,14824,14826],{"id":14825},"the-tool-that-worked-too-well","The Tool That Worked Too Well",[842,14828,14829],{},"In early 2025, we delivered a custom streaming data platform to a well-established independent label. The stack was a web application backed by a search engine optimized for analytical queries, hosted on a managed cloud provider.",[842,14831,14832],{},"The platform replaced a workflow that previously took days of manual spreadsheet work. It let their team filter, aggregate, and export royalty data across all their distribution partners from a single interface.",[842,14834,14835],{},"By mid-2025, both the data manager and the head of operations were using it regularly. The head of operations specifically praised how much time it saved during quarterly reporting.",[1572,14837,14838],{},[842,14839,14840],{},"This is the paradox: the better a tool works, the less visible its maintenance needs become. When everything runs smoothly, \"maintenance\" feels like paying for nothing.",[863,14842,14844],{"id":14843},"the-negotiation-spiral","The Negotiation Spiral",[842,14846,14847],{},"With our lead developer becoming less available, my business partner reached out to the label with a proposal: set aside a few hours each month for a dedicated developer who would monitor the system and handle issues proactively.",[842,14849,14850],{},"What followed was a months-long negotiation that perfectly illustrates the gap between how builders and clients think about software maintenance.",[1045,14852,14855,14873,14886,14896],{"className":14853},[1048,1049,1765,14854,1052],"gap-6",[1054,14856,14858,14865,14870],{"icon":3850,"title":14857},"Round 1: The Initial Proposal",[842,14859,14860,14861,14864],{},"We proposed a ",[996,14862,14863],{},"monthly retainer",", a small block of hours from a developer who already knew the system. Time logged, extras billed proportionally.",[842,14866,14867],{},[964,14868,14869],{},"\"We aren't sure that we'll need that many hours of help a month. Some of the problems we've had recently have been more with the files we've been supplied, rather than the tool itself.\"",[842,14871,14872],{},"The data manager asked about an ad-hoc rate instead.",[1054,14874,14876,14883],{"icon":11617,"title":14875},"Round 2: Meeting in the Middle",[842,14877,14878,14879,14882],{},"We adjusted. Instead of a monthly retainer, we offered ",[996,14880,14881],{},"a prepaid hour bank",", usable anytime over a full year. Essentially pay-as-you-go with a small upfront commitment.",[842,14884,14885],{},"The data manager countered with fewer hours and a request that any unused time roll over indefinitely.",[1054,14887,14890,14893],{"icon":14888,"title":14889},"i-lucide-shield-off","Round 3: The Stalemate",[842,14891,14892],{},"We came down further, offering flexible options with shorter commitment periods but no rollover. We explained that an open-ended rollover creates obligations that hang indefinitely, especially for a system that rarely needs help.",[842,14894,14895],{},"The data manager countered again with fewer hours and rollover. We couldn't go below our minimum: the smallest package that justified onboarding a new developer onto the project.",[1054,14897,14900,14905],{"icon":14898,"title":14899},"i-lucide-door-open","The Walk-Away",[842,14901,14902],{},[964,14903,14904],{},"\"We aren't sure that we'll need that level of support, so we think it may be best if we try and find someone else who can provide support in a more ad-hoc way.\"",[842,14906,14907],{},"They decided to look for a third-party maintainer. We offered to help with the handover.",[1901,14909,14910],{},[842,14911,14912],{},"When a client walks away from a maintenance agreement, neither side is wrong. The client sees a stable system and doesn't want to pay for peace of mind. The builder sees accumulated technical debt and knows that \"stable\" is temporary without active care.",[863,14914,14916],{"id":14915},"the-monthly-crashes","The Monthly Crashes",[842,14918,14919],{},"Here's the timeline of what happened after the agreement fell through:",[871,14921,14922,14935],{},[874,14923,14924],{},[877,14925,14926,14929,14932],{},[880,14927,14928],{},"Month",[880,14930,14931],{},"Issue",[880,14933,14934],{},"Resolution",[887,14936,14937,14950,14963,14976],{},[877,14938,14939,14944,14947],{},[892,14940,14941],{},[996,14942,14943],{},"Month 1",[892,14945,14946],{},"Uploader stops working",[892,14948,14949],{},"We restarted servers",[877,14951,14952,14957,14960],{},[892,14953,14954],{},[996,14955,14956],{},"Month 2",[892,14958,14959],{},"Importer breaks: UI shows success but nothing processes",[892,14961,14962],{},"Emergency fix",[877,14964,14965,14970,14973],{},[892,14966,14967],{},[996,14968,14969],{},"Month 3",[892,14971,14972],{},"Uploader down again",[892,14974,14975],{},"Restart; Data manager asks for technical details to find a third party",[877,14977,14978,14983,14989],{},[892,14979,14980],{},[996,14981,14982],{},"Month 4",[892,14984,14985,14986],{},"Uploader down ",[964,14987,14988],{},"again",[892,14990,14991],{},"Root cause found: security exploit on exposed service",[842,14993,14994,14995],{},"After four rounds, their data manager wrote: ",[964,14996,14997],{},"\"This does seem to happen every month now. Maybe if it's easy enough you can send me instructions on how to re-start the tool?\"",[842,14999,15000],{},"They were still searching for someone to take over ad-hoc maintenance. Months later, no one had been found.",[842,15002,15003],{},[1027,15004],{"alt":15005,"src":15006},"Server rack in dark room, unmaintained systems quietly accumulating risk","/images/blog/musictechlab_blog_sla-dilemma-server-rack.webp",[863,15008,15010],{"id":15009},"the-security-debt-nobody-saw","The Security Debt Nobody Saw",[842,15012,15013,15014,15017],{},"The fourth crash revealed something more serious than a simple restart issue. When we investigated, we found that ",[996,15015,15016],{},"a core infrastructure service had crashed due to a security exploit attempt",". The service was exposed to the public internet, running an outdated version with insufficient access controls.",[1032,15019,15020],{},[842,15021,15022],{},"This is what \"no maintenance\" actually looks like. It's not just restarts. It's unpatched services, exposed ports, and outdated dependencies quietly accumulating risk until something breaks or, worse, gets exploited.",[842,15024,15025],{},"The fix required:",[958,15027,15028,15031,15034,15037,15040],{},[961,15029,15030],{},"Binding the service to localhost only",[961,15032,15033],{},"Rotating credentials",[961,15035,15036],{},"Locking down external access",[961,15038,15039],{},"Upgrading to the latest stable version",[961,15041,15042],{},"Applying security patches",[842,15044,15045],{},"None of this would have been caught by \"just restarting the tool.\" And none of it would have been needed if someone had been proactively monitoring the system.",[863,15047,15049],{"id":15048},"the-just-restart-it-trap","The \"Just Restart It\" Trap",[842,15051,15052],{},"The data manager's request for restart instructions was perfectly logical. The symptom was always the same: uploader or importer stops working. The fix appeared to be a simple service restart. Why not just do it yourself?",[842,15054,15055],{},"Because restarting masks the root cause. In this case:",[958,15057,15058,15064,15070,15077],{},[961,15059,5119,15060,15063],{},[996,15061,15062],{},"uploader"," crashed because a backend service crashed",[961,15065,5119,15066,15069],{},[996,15067,15068],{},"backend service"," crashed because of an exploit attempt on an exposed port",[961,15071,15072,15073,15076],{},"The port was ",[996,15074,15075],{},"exposed"," because no one had hardened the configuration after deployment",[961,15078,15079,15080,15083],{},"The configuration was ",[996,15081,15082],{},"unhardened"," because there was no maintenance agreement",[842,15085,15086],{},"Each restart bought a month. Each month, the underlying problem grew worse.",[842,15088,15089],{},[1027,15090],{"alt":15091,"src":15092},"Man holding head in frustration at desk with laptop, the reality of searching for a third-party maintainer","/images/blog/musictechlab_blog_sla-dilemma-frustration.webp",[863,15094,15096],{"id":15095},"what-we-learned","What We Learned",[1074,15098,15100],{"id":15099},"for-builders-price-for-availability-not-just-hours","For builders: price for availability, not just hours",[842,15102,15103,15104,15107,15108,15111],{},"The biggest mistake we made was framing the discussion around ",[964,15105,15106],{},"hours of work",". When the system is stable, hours feel abstract. What the client actually needs is ",[996,15109,15110],{},"availability",": someone who picks up the phone (or email) when the importer breaks on the day they need to run monthly figures.",[1572,15113,15114],{},[842,15115,15116],{},"If we did this again, we would frame it differently: \"For X per month, you get a named contact who monitors your system, responds within 24 hours, and handles up to Y requests. No unused hours to argue about.\"",[1074,15118,15120],{"id":15119},"for-clients-maintenance-is-insurance-not-a-service","For clients: maintenance is insurance, not a service",[842,15122,15123,15124],{},"The label's reasoning was sound: ",[964,15125,15126],{},"\"We have relatively few problems with the tool, outside of this server restart issue which comes up fairly often but seems to be very quick to fix.\"",[842,15128,15129],{},"But that's exactly how insurance works. The claim is rare and the resolution is quick, until it isn't. The exploit could have resulted in data loss. The monthly crashes disrupted their workflow at the worst possible time (when royalty reports were due).",[1074,15131,15133],{"id":15132},"for-everyone-the-handover-gap-is-real","For everyone: the handover gap is real",[842,15135,15136],{},"The label spent months looking for a third-party maintainer. When they asked about the stack, we shared the full technical details. Simple enough on paper. But finding someone willing to take on ad-hoc maintenance of a system they didn't build, for a client they have no relationship with, at an unpredictable cadence, is genuinely hard.",[1901,15138,15139],{},[842,15140,15141],{},"The original builder is almost always the cheapest and fastest option for maintenance. They know the codebase, the infrastructure, and the client's workflow. Every handover involves a ramp-up period where the new maintainer is slower, more expensive, and more likely to introduce regressions.",[863,15143,15145],{"id":15144},"a-framework-for-music-tech-maintenance-agreements","A Framework for Music Tech Maintenance Agreements",[842,15147,15148],{},"Based on this experience and others, here's what we now recommend:",[1045,15150,15152,15171,15194],{"className":15151},[1048,1049,1050,14854,1052],[1054,15153,15155,15166],{"icon":3915,"title":15154},"Tier 1: Monitoring Only",[958,15156,15157,15160,15163],{},[961,15158,15159],{},"Automated uptime monitoring with alerts",[961,15161,15162],{},"Quarterly security patch review",[961,15164,15165],{},"Email response within 48 hours",[842,15167,15168],{},[964,15169,15170],{},"Best for: stable tools with minimal user interaction",[1054,15172,15175,15189],{"icon":15173,"title":15174},"i-lucide-life-buoy","Tier 2: Reactive Support",[958,15176,15177,15180,15183,15186],{},[961,15178,15179],{},"Everything in Tier 1",[961,15181,15182],{},"Named developer contact",[961,15184,15185],{},"Response within 24 hours on business days",[961,15187,15188],{},"Small prepaid hour bank (5-10 hours/year)",[842,15190,15191],{},[964,15192,15193],{},"Best for: tools used regularly but not business-critical daily",[1054,15195,15197,15211],{"icon":1057,"title":15196},"Tier 3: Proactive Maintenance",[958,15198,15199,15202,15205,15208],{},[961,15200,15201],{},"Everything in Tier 2",[961,15203,15204],{},"Monthly health checks (logs, disk space, dependencies)",[961,15206,15207],{},"Proactive security patches and version upgrades",[961,15209,15210],{},"Response within 4 hours on business days",[842,15212,15213],{},[964,15214,15215],{},"Best for: tools that are part of monthly business operations",[863,15217,15219],{"id":15218},"the-ending-so-far","The Ending (So Far)",[842,15221,15222,15223],{},"After the security incident, their data manager acknowledged that price had been the blocker all along. He opened the door to a new conversation: ",[964,15224,15225],{},"\"Happy to chat about that again if you think you can offer something more flexible given the low amount of maintenance that's needed.\"",[842,15227,15228],{},"We started talking again. This time, both sides had a much clearer picture of what \"low amount of maintenance\" actually meant, and what it cost when nobody did it.",[4937,15230],{},[842,15232,15233],{},[964,15234,15235],{},"Building a custom music data tool? Think about maintenance before you ship. The best time to set up a support agreement is during development, when both sides understand the system and the stakes. The second-best time is before the first crash.",{"title":728,"searchDepth":729,"depth":729,"links":15237},[15238,15239,15240,15241,15242,15243,15248,15249],{"id":14825,"depth":729,"text":14826},{"id":14843,"depth":729,"text":14844},{"id":14915,"depth":729,"text":14916},{"id":15009,"depth":729,"text":15010},{"id":15048,"depth":729,"text":15049},{"id":15095,"depth":729,"text":15096,"children":15244},[15245,15246,15247],{"id":15099,"depth":1112,"text":15100},{"id":15119,"depth":1112,"text":15120},{"id":15132,"depth":1112,"text":15133},{"id":15144,"depth":729,"text":15145},{"id":15218,"depth":729,"text":15219},"2026-02-24T00:00:00.000Z","What happens when a custom streaming analytics tool works perfectly, until nobody is responsible for keeping it running. A real story from the music industry.",[15253,15256,15259],{"question":15254,"answer":15255},"Why do custom music tech tools need ongoing maintenance?","Even stable tools require server restarts, security patches, dependency upgrades, and adapting to new DSP file formats. Without a maintenance agreement, small issues compound into outages.",{"question":15257,"answer":15258},"What is a good SLA model for indie labels with custom tools?","A small prepaid retainer (5-12 hours per year) with a dedicated contact works best. Pay-as-you-go sounds appealing but leaves no one accountable for monitoring or proactive fixes.",{"question":15260,"answer":15261},"How much does music tech tool maintenance typically cost?","Costs vary by complexity, but a small prepaid hour bank or a lightweight monthly retainer is typically the most cost-effective model. Ad-hoc support without an agreement usually ends up costing more due to context-switching and emergency response.",{"src":15263},"/images/blog/musictechlab_blog_sla-dilemma-hero.webp",{"enabled":738,"items":15265},[15266,15268,15270,15272],{"text":15267,"icon":3847},"A tool that crashed monthly had an exposed service exploited due to zero maintenance.",{"text":15269,"icon":4845},"The original builder is almost always the cheapest and fastest option for ongoing support.",{"text":15271,"icon":11617},"Frame maintenance as availability (named contact, SLA) not hours of work.",{"text":15273,"icon":3271},"A small prepaid retainer of 5-12 hours per year prevents compounding technical debt.",{},{"title":15276,"description":15277},"Music Tech Tool Maintenance: The SLA Dilemma | MusicTech Lab","What happens when nobody maintains a custom music data tool? Lessons from a real indie label engagement on SLAs, pricing, and security debt.",[5523,15279],"devops","qPydmx4k5XgqDRcU2H6KWeVwgdx4wUKMmmZNmJtJl0Q",{"id":15282,"title":108,"authors":15283,"badge":15286,"body":15288,"category":731,"client":723,"date":16543,"description":16544,"extension":734,"faq":16545,"featured":69,"featuredOrder":723,"hidden":69,"image":16554,"keyTakeaways":16556,"meta":16564,"navigation":738,"path":109,"seo":16565,"status":723,"stem":110,"tags":16568,"teaser":723,"__hash__":16569},"posts/blog/music-data/building-a-claude-skill-for-ddex-validation-music-metadata.md",[15284],{"name":834,"to":720,"avatar":15285},{"src":722},{"label":15287,"color":838},"Tutorial",{"type":725,"value":15289,"toc":16526},[15290,15293,15296,15307,15321,15325,15332,15338,15345,15349,15364,15368,15371,15386,15390,15393,15418,15422,15432,15438,15442,15445,15532,15553,15557,15560,16009,16013,16016,16124,16137,16141,16144,16151,16154,16161,16175,16179,16182,16190,16198,16224,16228,16231,16237,16243,16249,16364,16370,16374,16377,16426,16429,16444,16448,16455,16485,16496,16500,16506,16509,16523],[842,15291,15292],{},"If you distribute music digitally, you've dealt with DDEX. The Electronic Release Notification (ERN) standard is how labels and distributors exchange release metadata - track titles, ISRC codes, territory rights, audio file references, and more. Get it wrong, and your release gets rejected. Get it right, and it flows seamlessly to Spotify, Apple Music, and hundreds of other platforms.",[842,15294,15295],{},"The problem? DDEX XML files are complex, deeply nested, and easy to break. Validation usually means uploading files to web tools, running CLI scripts, or waiting for your distributor to reject your submission.",[842,15297,15298,15299,15302,15303,15306],{},"In this tutorial, we'll build a ",[996,15300,15301],{},"Claude skill"," that validates DDEX ERN files on demand - right inside Claude Code or Claude.ai. Type ",[895,15304,15305],{},"/ddex-validate",", paste your XML, and get instant feedback with actionable error messages.",[1572,15308,15309],{},[842,15310,15311,15312,15315,15316,861],{},"This is a ",[996,15313,15314],{},"proof of concept"," - an experiment in using AI to streamline daily routines when working with music industry data. The full skill is open source on ",[846,15317,15320],{"href":15318,"rel":15319},"https://github.com/musictechlab/ddex-validate",[850],"GitHub",[863,15322,15324],{"id":15323},"what-is-a-claude-skill","What is a Claude skill?",[842,15326,15327,15328,15331],{},"A skill is a folder with a ",[895,15329,15330],{},"SKILL.md"," file that teaches Claude how to handle a specific workflow. Think of it as a reusable prompt template with structure:",[1013,15333,15336],{"className":15334,"code":15335,"language":1018,"meta":728},[1016],"ddex-validate/\n├── SKILL.md                    # Instructions + YAML frontmatter\n├── references/\n│   └── ern-structure.md        # DDEX domain knowledge\n└── assets/\n    └── ern382-sample.xml       # Example file for testing\n",[895,15337,15335],{"__ignoreMap":728},[842,15339,15340,15341,15344],{},"Skills use ",[996,15342,15343],{},"progressive disclosure"," - Claude loads the frontmatter first (to decide if the skill is relevant), then the full instructions only when triggered. This keeps token usage low while maintaining deep domain expertise.",[863,15346,15348],{"id":15347},"prerequisites","Prerequisites",[958,15350,15351,15358,15361],{},[961,15352,15353,15357],{},[846,15354,8749],{"href":15355,"rel":15356},"https://docs.anthropic.com/en/docs/claude-code/overview",[850]," installed (or Claude.ai with skills support)",[961,15359,15360],{},"Basic familiarity with XML and DDEX concepts",[961,15362,15363],{},"About 15–30 minutes",[863,15365,15367],{"id":15366},"step-1-plan-the-use-cases","Step 1: Plan the use cases",[842,15369,15370],{},"Before writing any code, define what the skill should handle:",[1045,15372,15374,15378,15382],{"className":15373},[1048,1049,1050,1051,1052],[1054,15375],{"description":15376,"title":15377},"Parse XML, detect ERN version, fetch XSD schema, validate, and report errors with line numbers.","Full XML Validation",[1054,15379],{"description":15380,"title":15381},"Verify required elements exist and check ISRC/UPC format without full schema validation.","Quick Structure Check",[1054,15383],{"description":15384,"title":15385},"Analyze each error, suggest a specific fix, and show corrected XML snippets the user can copy-paste.","Fix Suggestions",[863,15387,15389],{"id":15388},"step-2-create-the-skill-folder","Step 2: Create the skill folder",[842,15391,15392],{},"Create the skill directory in your Claude Code skills location:",[1013,15394,15396],{"className":1080,"code":15395,"language":1082,"meta":728,"style":728},"mkdir -p ~/.claude/skills/ddex-validate/references\nmkdir -p ~/.claude/skills/ddex-validate/assets\n",[895,15397,15398,15409],{"__ignoreMap":728},[1086,15399,15400,15403,15406],{"class":1088,"line":1089},[1086,15401,15402],{"class":1092},"mkdir",[1086,15404,15405],{"class":1096}," -p",[1086,15407,15408],{"class":1096}," ~/.claude/skills/ddex-validate/references\n",[1086,15410,15411,15413,15415],{"class":1088,"line":729},[1086,15412,15402],{"class":1092},[1086,15414,15405],{"class":1096},[1086,15416,15417],{"class":1096}," ~/.claude/skills/ddex-validate/assets\n",[863,15419,15421],{"id":15420},"step-3-write-the-skillmd","Step 3: Write the SKILL.md",[842,15423,15424,15425,15428,15429,861],{},"This is the core of the skill. The YAML frontmatter tells Claude ",[996,15426,15427],{},"when"," to load it, and the Markdown body tells Claude ",[996,15430,15431],{},"what to do",[842,15433,15434,15435,1133],{},"Create ",[895,15436,15437],{},"~/.claude/skills/ddex-validate/SKILL.md",[1074,15439,15441],{"id":15440},"the-frontmatter","The frontmatter",[842,15443,15444],{},"The frontmatter is the most important part - it's how Claude decides whether to load your skill:",[1013,15446,15450],{"className":15447,"code":15448,"language":15449,"meta":728,"style":728},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","name: ddex-validate\ndescription: Validates DDEX ERN XML files against official schemas.\n  Checks structure, required fields (ISRC, UPC, MessageHeader), and\n  schema conformance. Use when user uploads .xml files, mentions\n  \"DDEX\", \"ERN\", \"validate release\", \"check metadata\", or\n  \"release notification\".\n","yaml",[895,15451,15452,15461,15471,15476,15481,15520],{"__ignoreMap":728},[1086,15453,15454,15456,15458],{"class":1088,"line":1089},[1086,15455,4184],{"class":4109},[1086,15457,1133],{"class":1146},[1086,15459,15460],{"class":1096}," ddex-validate\n",[1086,15462,15463,15466,15468],{"class":1088,"line":729},[1086,15464,15465],{"class":4109},"description",[1086,15467,1133],{"class":1146},[1086,15469,15470],{"class":1096}," Validates DDEX ERN XML files against official schemas.\n",[1086,15472,15473],{"class":1088,"line":1112},[1086,15474,15475],{"class":1096},"  Checks structure, required fields (ISRC, UPC, MessageHeader), and\n",[1086,15477,15478],{"class":1088,"line":1181},[1086,15479,15480],{"class":1096},"  schema conformance. Use when user uploads .xml files, mentions\n",[1086,15482,15483,15485,15487,15489,15491,15493,15495,15497,15499,15501,15504,15506,15508,15510,15513,15515,15517],{"class":1088,"line":1205},[1086,15484,1152],{"class":1146},[1086,15486,9763],{"class":1096},[1086,15488,1159],{"class":1146},[1086,15490,5660],{"class":1436},[1086,15492,1159],{"class":1146},[1086,15494,9881],{"class":1096},[1086,15496,1159],{"class":1146},[1086,15498,5660],{"class":1436},[1086,15500,1159],{"class":1146},[1086,15502,15503],{"class":1096},"validate release",[1086,15505,1159],{"class":1146},[1086,15507,5660],{"class":1436},[1086,15509,1159],{"class":1146},[1086,15511,15512],{"class":1096},"check metadata",[1086,15514,1159],{"class":1146},[1086,15516,5660],{"class":1436},[1086,15518,15519],{"class":1096},"or\n",[1086,15521,15522,15524,15527,15529],{"class":1088,"line":1276},[1086,15523,1152],{"class":1146},[1086,15525,15526],{"class":1096},"release notification",[1086,15528,1159],{"class":1146},[1086,15530,15531],{"class":1187},".\n",[1032,15533,15534],{},[842,15535,15536,15537,15541,15542,15541,15547,15552],{},"A good description follows the pattern: ",[996,15538,15539],{},[1086,15540,885],{}," + ",[996,15543,15544],{},[1086,15545,15546],{},"When to use it",[996,15548,15549],{},[1086,15550,15551],{},"Key capabilities",". Include specific trigger phrases users might say - Claude uses these to decide if the skill is relevant.",[1074,15554,15556],{"id":15555},"the-validation-workflow","The validation workflow",[842,15558,15559],{},"The body of SKILL.md contains step-by-step instructions. Here's the core - the Python validation function that fetches the official DDEX XSD and validates against it:",[1013,15561,15564],{"className":1368,"code":15562,"filename":15563,"language":1250,"meta":728,"style":728},"from lxml import etree\nimport requests\n\ndef validate_ddex(xml_content: str, version: str) -> dict:\n    \"\"\"Validate DDEX XML against official ERN schema.\"\"\"\n    xsd_url = (\n        f\"https://service.ddex.net/xml/ern/\"\n        f\"{version}/release-notification.xsd\"\n    )\n\n    # Fetch and compile schema\n    response = requests.get(xsd_url, timeout=10)\n    response.raise_for_status()\n    schema_root = etree.fromstring(response.content)\n    schema = etree.XMLSchema(schema_root)\n\n    # Parse and validate\n    xml_tree = etree.fromstring(xml_content.encode())\n    is_valid = schema.validate(xml_tree)\n\n    errors = []\n    if not is_valid:\n        for error in schema.error_log:\n            errors.append({\n                \"line\": error.line,\n                \"column\": error.column,\n                \"message\": error.message,\n                \"level\": error.level_name,\n            })\n\n    return {\"valid\": is_valid, \"errors\": errors, \"version\": version}\n","validate_ddex.py",[895,15565,15566,15579,15586,15590,15623,15632,15641,15648,15664,15668,15672,15677,15707,15719,15746,15767,15771,15776,15801,15823,15827,15836,15847,15865,15877,15897,15916,15935,15955,15960,15964],{"__ignoreMap":728},[1086,15567,15568,15571,15574,15576],{"class":1088,"line":1089},[1086,15569,15570],{"class":1423},"from",[1086,15572,15573],{"class":1436}," lxml ",[1086,15575,6503],{"class":1423},[1086,15577,15578],{"class":1436}," etree\n",[1086,15580,15581,15583],{"class":1088,"line":729},[1086,15582,6503],{"class":1423},[1086,15584,15585],{"class":1436}," requests\n",[1086,15587,15588],{"class":1088,"line":1112},[1086,15589,3390],{"emptyLinePlaceholder":738},[1086,15591,15592,15594,15597,15599,15602,15604,15606,15608,15611,15613,15615,15617,15619,15621],{"class":1088,"line":1181},[1086,15593,1392],{"class":1155},[1086,15595,15596],{"class":1105}," validate_ddex",[1086,15598,1398],{"class":1146},[1086,15600,15601],{"class":1401},"xml_content",[1086,15603,1133],{"class":1146},[1086,15605,1407],{"class":1092},[1086,15607,1227],{"class":1146},[1086,15609,15610],{"class":1401}," version",[1086,15612,1133],{"class":1146},[1086,15614,1407],{"class":1092},[1086,15616,1410],{"class":1146},[1086,15618,1413],{"class":1146},[1086,15620,1518],{"class":1092},[1086,15622,1418],{"class":1146},[1086,15624,15625,15627,15630],{"class":1088,"line":1205},[1086,15626,1424],{"class":1423},[1086,15628,15629],{"class":1427},"Validate DDEX XML against official ERN schema.",[1086,15631,1431],{"class":1423},[1086,15633,15634,15637,15639],{"class":1088,"line":1276},[1086,15635,15636],{"class":1436},"    xsd_url ",[1086,15638,1440],{"class":1146},[1086,15640,6041],{"class":1146},[1086,15642,15643,15645],{"class":1088,"line":1282},[1086,15644,6058],{"class":1155},[1086,15646,15647],{"class":1096},"\"https://service.ddex.net/xml/ern/\"\n",[1086,15649,15650,15652,15654,15656,15659,15661],{"class":1088,"line":1288},[1086,15651,6058],{"class":1155},[1086,15653,1159],{"class":1096},[1086,15655,4409],{"class":1187},[1086,15657,15658],{"class":1436},"version",[1086,15660,4423],{"class":1187},[1086,15662,15663],{"class":1096},"/release-notification.xsd\"\n",[1086,15665,15666],{"class":1088,"line":2685},[1086,15667,6219],{"class":1146},[1086,15669,15670],{"class":1088,"line":2700},[1086,15671,3390],{"emptyLinePlaceholder":738},[1086,15673,15674],{"class":1088,"line":3398},[1086,15675,15676],{"class":1427},"    # Fetch and compile schema\n",[1086,15678,15679,15682,15684,15687,15689,15691,15693,15696,15698,15701,15703,15705],{"class":1088,"line":1715},[1086,15680,15681],{"class":1436},"    response ",[1086,15683,1440],{"class":1146},[1086,15685,15686],{"class":1436}," requests",[1086,15688,861],{"class":1146},[1086,15690,10812],{"class":1105},[1086,15692,1398],{"class":1146},[1086,15694,15695],{"class":1105},"xsd_url",[1086,15697,1227],{"class":1146},[1086,15699,15700],{"class":1401}," timeout",[1086,15702,1440],{"class":1146},[1086,15704,7284],{"class":1187},[1086,15706,1455],{"class":1146},[1086,15708,15709,15712,15714,15717],{"class":1088,"line":3409},[1086,15710,15711],{"class":1436},"    response",[1086,15713,861],{"class":1146},[1086,15715,15716],{"class":1105},"raise_for_status",[1086,15718,1387],{"class":1146},[1086,15720,15721,15724,15726,15729,15731,15734,15736,15739,15741,15744],{"class":1088,"line":3415},[1086,15722,15723],{"class":1436},"    schema_root ",[1086,15725,1440],{"class":1146},[1086,15727,15728],{"class":1436}," etree",[1086,15730,861],{"class":1146},[1086,15732,15733],{"class":1105},"fromstring",[1086,15735,1398],{"class":1146},[1086,15737,15738],{"class":1105},"response",[1086,15740,861],{"class":1146},[1086,15742,15743],{"class":4109},"content",[1086,15745,1455],{"class":1146},[1086,15747,15748,15751,15753,15755,15757,15760,15762,15765],{"class":1088,"line":3421},[1086,15749,15750],{"class":1436},"    schema ",[1086,15752,1440],{"class":1146},[1086,15754,15728],{"class":1436},[1086,15756,861],{"class":1146},[1086,15758,15759],{"class":1105},"XMLSchema",[1086,15761,1398],{"class":1146},[1086,15763,15764],{"class":1105},"schema_root",[1086,15766,1455],{"class":1146},[1086,15768,15769],{"class":1088,"line":3427},[1086,15770,3390],{"emptyLinePlaceholder":738},[1086,15772,15773],{"class":1088,"line":3433},[1086,15774,15775],{"class":1427},"    # Parse and validate\n",[1086,15777,15778,15781,15783,15785,15787,15789,15791,15793,15795,15798],{"class":1088,"line":3439},[1086,15779,15780],{"class":1436},"    xml_tree ",[1086,15782,1440],{"class":1146},[1086,15784,15728],{"class":1436},[1086,15786,861],{"class":1146},[1086,15788,15733],{"class":1105},[1086,15790,1398],{"class":1146},[1086,15792,15601],{"class":1105},[1086,15794,861],{"class":1146},[1086,15796,15797],{"class":1105},"encode",[1086,15799,15800],{"class":1146},"())\n",[1086,15802,15803,15806,15808,15811,15813,15816,15818,15821],{"class":1088,"line":3444},[1086,15804,15805],{"class":1436},"    is_valid ",[1086,15807,1440],{"class":1146},[1086,15809,15810],{"class":1436}," schema",[1086,15812,861],{"class":1146},[1086,15814,15815],{"class":1105},"validate",[1086,15817,1398],{"class":1146},[1086,15819,15820],{"class":1105},"xml_tree",[1086,15822,1455],{"class":1146},[1086,15824,15825],{"class":1088,"line":3450},[1086,15826,3390],{"emptyLinePlaceholder":738},[1086,15828,15829,15832,15834],{"class":1088,"line":3456},[1086,15830,15831],{"class":1436},"    errors ",[1086,15833,1440],{"class":1146},[1086,15835,5920],{"class":1146},[1086,15837,15838,15840,15842,15845],{"class":1088,"line":3462},[1086,15839,6474],{"class":1423},[1086,15841,6803],{"class":1146},[1086,15843,15844],{"class":1436}," is_valid",[1086,15846,1418],{"class":1146},[1086,15848,15849,15851,15854,15856,15858,15860,15863],{"class":1088,"line":3467},[1086,15850,6903],{"class":1423},[1086,15852,15853],{"class":1436}," error ",[1086,15855,5931],{"class":1423},[1086,15857,15810],{"class":1436},[1086,15859,861],{"class":1146},[1086,15861,15862],{"class":4109},"error_log",[1086,15864,1418],{"class":1146},[1086,15866,15867,15870,15872,15874],{"class":1088,"line":3473},[1086,15868,15869],{"class":1436},"            errors",[1086,15871,861],{"class":1146},[1086,15873,5957],{"class":1105},[1086,15875,15876],{"class":1146},"({\n",[1086,15878,15879,15882,15884,15886,15888,15891,15893,15895],{"class":1088,"line":3479},[1086,15880,15881],{"class":1146},"                \"",[1086,15883,1088],{"class":1096},[1086,15885,1159],{"class":1146},[1086,15887,1133],{"class":1146},[1086,15889,15890],{"class":1105}," error",[1086,15892,861],{"class":1146},[1086,15894,1088],{"class":4109},[1086,15896,1202],{"class":1146},[1086,15898,15899,15901,15904,15906,15908,15910,15912,15914],{"class":1088,"line":3485},[1086,15900,15881],{"class":1146},[1086,15902,15903],{"class":1096},"column",[1086,15905,1159],{"class":1146},[1086,15907,1133],{"class":1146},[1086,15909,15890],{"class":1105},[1086,15911,861],{"class":1146},[1086,15913,15903],{"class":4109},[1086,15915,1202],{"class":1146},[1086,15917,15918,15920,15923,15925,15927,15929,15931,15933],{"class":1088,"line":3491},[1086,15919,15881],{"class":1146},[1086,15921,15922],{"class":1096},"message",[1086,15924,1159],{"class":1146},[1086,15926,1133],{"class":1146},[1086,15928,15890],{"class":1105},[1086,15930,861],{"class":1146},[1086,15932,15922],{"class":4109},[1086,15934,1202],{"class":1146},[1086,15936,15937,15939,15942,15944,15946,15948,15950,15953],{"class":1088,"line":3497},[1086,15938,15881],{"class":1146},[1086,15940,15941],{"class":1096},"level",[1086,15943,1159],{"class":1146},[1086,15945,1133],{"class":1146},[1086,15947,15890],{"class":1105},[1086,15949,861],{"class":1146},[1086,15951,15952],{"class":4109},"level_name",[1086,15954,1202],{"class":1146},[1086,15956,15957],{"class":1088,"line":3503},[1086,15958,15959],{"class":1146},"            })\n",[1086,15961,15962],{"class":1088,"line":3509},[1086,15963,3390],{"emptyLinePlaceholder":738},[1086,15965,15966,15968,15970,15972,15975,15977,15979,15981,15983,15985,15988,15990,15992,15995,15997,15999,16001,16003,16005,16007],{"class":1088,"line":3515},[1086,15967,1460],{"class":1423},[1086,15969,4520],{"class":1146},[1086,15971,1159],{"class":1146},[1086,15973,15974],{"class":1096},"valid",[1086,15976,1159],{"class":1146},[1086,15978,1133],{"class":1146},[1086,15980,15844],{"class":1436},[1086,15982,1227],{"class":1146},[1086,15984,1195],{"class":1146},[1086,15986,15987],{"class":1096},"errors",[1086,15989,1159],{"class":1146},[1086,15991,1133],{"class":1146},[1086,15993,15994],{"class":1436}," errors",[1086,15996,1227],{"class":1146},[1086,15998,1195],{"class":1146},[1086,16000,15658],{"class":1096},[1086,16002,1159],{"class":1146},[1086,16004,1133],{"class":1146},[1086,16006,15610],{"class":1436},[1086,16008,1291],{"class":1146},[1074,16010,16012],{"id":16011},"music-industry-checks","Music industry checks",[842,16014,16015],{},"Beyond schema validation, the skill checks for issues that cause real distributor rejections:",[871,16017,16018,16029],{},[874,16019,16020],{},[877,16021,16022,16024,16027],{},[880,16023,10540],{},[880,16025,16026],{},"Rule",[880,16028,7741],{},[887,16030,16031,16043,16056,16073,16085,16108],{},[877,16032,16033,16035,16038],{},[892,16034,3856],{},[892,16036,16037],{},"Exactly 12 alphanumeric chars, no hyphens",[892,16039,16040],{},[895,16041,16042],{},"USSM12345678",[877,16044,16045,16048,16051],{},[892,16046,16047],{},"UPC/EAN",[892,16049,16050],{},"12 or 13 digits",[892,16052,16053],{},[895,16054,16055],{},"123456789012",[877,16057,16058,16060,16065],{},[892,16059,10395],{},[892,16061,16062,16063],{},"ISO 3166-1 alpha-2 or ",[895,16064,10398],{},[892,16066,16067,5660,16069,5660,16071],{},[895,16068,13326],{},[895,16070,13347],{},[895,16072,10398],{},[877,16074,16075,16077,16080],{},[892,16076,11305],{},[892,16078,16079],{},"ISO 8601 format",[892,16081,16082],{},[895,16083,16084],{},"PT3M30S",[877,16086,16087,16090,16100],{},[892,16088,16089],{},"Resource refs",[892,16091,16092,16093,16096,16097],{},"Every ",[895,16094,16095],{},"ReleaseResourceReference"," must match a ",[895,16098,16099],{},"ResourceReference",[892,16101,16102,16105,16106],{},[895,16103,16104],{},"A1"," links to ",[895,16107,16104],{},[877,16109,16110,16113,16116],{},[892,16111,16112],{},"ParentalWarning",[892,16114,16115],{},"Must be present",[892,16117,16118,5660,16121],{},[895,16119,16120],{},"NotExplicit",[895,16122,16123],{},"Explicit",[1901,16125,16126],{},[842,16127,16128,16129,16132,16133,16136],{},"ISRC codes use hyphens for display (",[895,16130,16131],{},"US-SM1-23-45678",") but ",[996,16134,16135],{},"never in XML",". This is one of the most common validation errors we see.",[1074,16138,16140],{"id":16139},"output-format","Output format",[842,16142,16143],{},"The skill defines exactly how Claude should present results:",[1013,16145,16149],{"className":16146,"code":16147,"filename":16148,"language":1018,"meta":728},[1016],"DDEX ERN 382 - Valid\n\nSummary:\n- Releases: 1\n- Sound Recordings: 3\n- Images: 1\n- Territory: Worldwide\n- Message Type: LiveMessage\n","valid output",[895,16150,16147],{"__ignoreMap":728},[842,16152,16153],{},"And when errors are found:",[1013,16155,16159],{"className":16156,"code":16157,"filename":16158,"language":1018,"meta":728},[1016],"DDEX ERN 382 - 2 error(s) found\n\n| # | Line | Error                           | Suggested Fix                    |\n|---|------|---------------------------------|----------------------------------|\n| 1 | 36   | Element 'ISRC': '' is not valid | Add 12-char ISRC: CCXXXYYNNNNN   |\n| 2 | 45   | Missing element 'TerritoryCode' | Add \u003CTerritoryCode>Worldwide\u003C/…> |\n","error output",[895,16160,16157],{"__ignoreMap":728},[1572,16162,16163],{},[842,16164,16165,16166,16169,16170,861],{},"The full SKILL.md also includes a ",[996,16167,16168],{},"Common Issues"," section with pre-documented fixes for namespace errors, resource reference mismatches, and territory code problems. See the complete file in the ",[846,16171,16174],{"href":16172,"rel":16173},"https://github.com/musictechlab/ddex-validate/blob/main/SKILL.md",[850],"GitHub repo",[863,16176,16178],{"id":16177},"step-4-add-supporting-files","Step 4: Add supporting files",[842,16180,16181],{},"The skill needs two more pieces:",[842,16183,16184,16189],{},[996,16185,16186],{},[895,16187,16188],{},"references/ern-structure.md"," - the complete ERN element hierarchy (MessageHeader, ResourceList, ReleaseList, DealList), field format rules, and version differences between ERN 3.8.2 and 4.1.1+. Claude loads this on demand when it needs deeper context during validation.",[842,16191,16192,16197],{},[996,16193,16194],{},[895,16195,16196],{},"assets/ern382-sample.xml"," - a minimal but valid ERN 3.8.2 file with a MessageHeader, one SoundRecording (ISRC, artist, genre, technical details), one Image, and one Release.",[1032,16199,16200,16207],{},[842,16201,16202,16203,16206],{},"You don't need to write these from scratch. Clone the ",[846,16204,16174],{"href":15318,"rel":16205},[850]," and the full skill is ready to use:",[1013,16208,16210],{"className":1080,"code":16209,"language":1082,"meta":728,"style":728},"git clone https://github.com/musictechlab/ddex-validate.git ~/.claude/skills/ddex-validate\n",[895,16211,16212],{"__ignoreMap":728},[1086,16213,16214,16216,16218,16221],{"class":1088,"line":1089},[1086,16215,1093],{"class":1092},[1086,16217,1097],{"class":1096},[1086,16219,16220],{"class":1096}," https://github.com/musictechlab/ddex-validate.git",[1086,16222,16223],{"class":1096}," ~/.claude/skills/ddex-validate\n",[863,16225,16227],{"id":16226},"step-5-test-the-skill","Step 5: Test the skill",[842,16229,16230],{},"Once installed, test it from three angles:",[842,16232,16233,16236],{},[996,16234,16235],{},"Should trigger"," - try prompts like these:",[1013,16238,16241],{"className":16239,"code":16240,"language":1018,"meta":728},[1016],"\"Validate this DDEX file\"\n\"Check my ERN XML for errors\"\n\"Is this release metadata valid?\"\n",[895,16242,16240],{"__ignoreMap":728},[842,16244,16245,16248],{},[996,16246,16247],{},"Error detection"," - introduce common mistakes and verify the skill catches them:",[1013,16250,16253],{"className":11154,"code":16251,"filename":16252,"language":11157,"meta":728,"style":728},"\u003C!-- Missing ISRC -->\n\u003CSoundRecordingId>\n  \u003CCatalogNumber Namespace=\"PADPIDA2023001\">CAT001\u003C/CatalogNumber>\n\u003C/SoundRecordingId>\n\n\u003C!-- Invalid territory code -->\n\u003CTerritoryCode>United States\u003C/TerritoryCode>\n\n\u003C!-- Mismatched resource reference -->\n\u003CReleaseResourceReference>A99\u003C/ReleaseResourceReference>\n\u003C!-- when ResourceReference is \"A1\" -->\n","common-errors.xml",[895,16254,16255,16260,16269,16299,16307,16311,16316,16333,16337,16342,16359],{"__ignoreMap":728},[1086,16256,16257],{"class":1088,"line":1089},[1086,16258,16259],{"class":1427},"\u003C!-- Missing ISRC -->\n",[1086,16261,16262,16264,16267],{"class":1088,"line":729},[1086,16263,11164],{"class":1146},[1086,16265,16266],{"class":4109},"SoundRecordingId",[1086,16268,11170],{"class":1146},[1086,16270,16271,16273,16276,16279,16281,16283,16286,16288,16290,16293,16295,16297],{"class":1088,"line":1112},[1086,16272,11180],{"class":1146},[1086,16274,16275],{"class":4109},"CatalogNumber",[1086,16277,16278],{"class":1155}," Namespace",[1086,16280,1440],{"class":1146},[1086,16282,1159],{"class":1146},[1086,16284,16285],{"class":1096},"PADPIDA2023001",[1086,16287,1159],{"class":1146},[1086,16289,2694],{"class":1146},[1086,16291,16292],{"class":1436},"CAT001",[1086,16294,11201],{"class":1146},[1086,16296,16275],{"class":4109},[1086,16298,11170],{"class":1146},[1086,16300,16301,16303,16305],{"class":1088,"line":1181},[1086,16302,11201],{"class":1146},[1086,16304,16266],{"class":4109},[1086,16306,11170],{"class":1146},[1086,16308,16309],{"class":1088,"line":1205},[1086,16310,3390],{"emptyLinePlaceholder":738},[1086,16312,16313],{"class":1088,"line":1276},[1086,16314,16315],{"class":1427},"\u003C!-- Invalid territory code -->\n",[1086,16317,16318,16320,16323,16325,16327,16329,16331],{"class":1088,"line":1282},[1086,16319,11164],{"class":1146},[1086,16321,16322],{"class":4109},"TerritoryCode",[1086,16324,2694],{"class":1146},[1086,16326,13321],{"class":1436},[1086,16328,11201],{"class":1146},[1086,16330,16322],{"class":4109},[1086,16332,11170],{"class":1146},[1086,16334,16335],{"class":1088,"line":1288},[1086,16336,3390],{"emptyLinePlaceholder":738},[1086,16338,16339],{"class":1088,"line":2685},[1086,16340,16341],{"class":1427},"\u003C!-- Mismatched resource reference -->\n",[1086,16343,16344,16346,16348,16350,16353,16355,16357],{"class":1088,"line":2700},[1086,16345,11164],{"class":1146},[1086,16347,16095],{"class":4109},[1086,16349,2694],{"class":1146},[1086,16351,16352],{"class":1436},"A99",[1086,16354,11201],{"class":1146},[1086,16356,16095],{"class":4109},[1086,16358,11170],{"class":1146},[1086,16360,16361],{"class":1088,"line":3398},[1086,16362,16363],{"class":1427},"\u003C!-- when ResourceReference is \"A1\" -->\n",[842,16365,16366,16369],{},[996,16367,16368],{},"Should NOT trigger"," - prompts like \"Write a Python function\" or \"Help me with CSS\" should not activate the skill.",[863,16371,16373],{"id":16372},"how-it-works-under-the-hood","How it works under the hood",[842,16375,16376],{},"When you type a prompt that matches the skill's description triggers, Claude's progressive disclosure system kicks in:",[1013,16378,16380],{"className":3341,"code":16379,"language":3343,"meta":728,"style":728},"graph LR\n    A[User prompt] --> B{Match?}\n    B -->|Yes| C[Level 1: Frontmatter]\n    B -->|No| D[Skip skill]\n    C --> E[Level 2: SKILL.md body]\n    E --> F{Need more context?}\n    F -->|Yes| G[Level 3: references/]\n    F -->|No| H[Execute validation]\n    G --> H\n",[895,16381,16382,16387,16392,16397,16402,16407,16412,16417,16422],{"__ignoreMap":728},[1086,16383,16384],{"class":1088,"line":1089},[1086,16385,16386],{"class":1436},"graph LR\n",[1086,16388,16389],{"class":1088,"line":729},[1086,16390,16391],{"class":1436},"    A[User prompt] --> B{Match?}\n",[1086,16393,16394],{"class":1088,"line":1112},[1086,16395,16396],{"class":1436},"    B -->|Yes| C[Level 1: Frontmatter]\n",[1086,16398,16399],{"class":1088,"line":1181},[1086,16400,16401],{"class":1436},"    B -->|No| D[Skip skill]\n",[1086,16403,16404],{"class":1088,"line":1205},[1086,16405,16406],{"class":1436},"    C --> E[Level 2: SKILL.md body]\n",[1086,16408,16409],{"class":1088,"line":1276},[1086,16410,16411],{"class":1436},"    E --> F{Need more context?}\n",[1086,16413,16414],{"class":1088,"line":1282},[1086,16415,16416],{"class":1436},"    F -->|Yes| G[Level 3: references/]\n",[1086,16418,16419],{"class":1088,"line":1288},[1086,16420,16421],{"class":1436},"    F -->|No| H[Execute validation]\n",[1086,16423,16424],{"class":1088,"line":2685},[1086,16425,3401],{"class":1436},[842,16427,16428],{},"This three-level system keeps token usage minimal for non-DDEX conversations while providing deep expertise when needed:",[1045,16430,16432,16436,16440],{"className":16431},[1048,1049,1050,1051,1052],[1054,16433],{"description":16434,"title":16435},"Claude reads `name` and `description`. Just enough to decide: \"is this a DDEX request?\"","Level 1 - Frontmatter",[1054,16437],{"description":16438,"title":16439},"The full 5-step workflow: parse, validate, check industry rules, report results.","Level 2 - Instructions",[1054,16441],{"description":16442,"title":16443},"Deep ERN structure docs, field formats, version differences. Loaded only when needed.","Level 3 - References",[863,16445,16447],{"id":16446},"whats-next","What's next",[842,16449,16450,16451,16454],{},"This skill covers ",[996,16452,16453],{},"standalone validation"," - no external services required. But there are natural extensions:",[958,16456,16457,16467,16473,16479],{},[961,16458,16459,16462,16463,16466],{},[996,16460,16461],{},"MCP integration"," - connect to your distributor's API to validate ",[964,16464,16465],{},"and"," submit in one workflow",[961,16468,16469,16472],{},[996,16470,16471],{},"Batch validation"," - process an entire directory of ERN files before a bulk delivery",[961,16474,16475,16478],{},[996,16476,16477],{},"DSRF support"," - extend to Digital Sales Report Format flat files (tab-separated, not XML)",[961,16480,16481,16484],{},[996,16482,16483],{},"Auto-fix mode"," - instead of just reporting errors, have Claude rewrite the XML with fixes applied",[842,16486,16487,16488,16491,16492,16495],{},"You could also combine this with an AudioSalad or Revelator delivery skill - validate the DDEX, then push it to your distributor, all from one conversation. We've already built similar Claude integrations: a ",[846,16489,16490],{"href":619},"SignNow MCP server"," for e-signatures and a ",[846,16493,16494],{"href":165},"Bandcamp MCP server"," for revenue analytics.",[863,16497,16499],{"id":16498},"wrapping-up","Wrapping up",[842,16501,16502,16503,16505],{},"DDEX validation is a perfect fit for a Claude skill: it's a well-defined workflow, requires domain knowledge that doesn't change often, and benefits from consistent execution. Instead of context-switching to a web tool or remembering CLI flags, you type ",[895,16504,15305],{}," and get expert-level feedback in seconds.",[842,16507,16508],{},"The full skill is open source - clone it and start validating:",[1013,16510,16511],{"className":1080,"code":16209,"language":1082,"meta":728,"style":728},[895,16512,16513],{"__ignoreMap":728},[1086,16514,16515,16517,16519,16521],{"class":1088,"line":1089},[1086,16516,1093],{"class":1092},[1086,16518,1097],{"class":1096},[1086,16520,16220],{"class":1096},[1086,16522,16223],{"class":1096},[1680,16524,16525],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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 .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}",{"title":728,"searchDepth":729,"depth":729,"links":16527},[16528,16529,16530,16531,16532,16538,16539,16540,16541,16542],{"id":15323,"depth":729,"text":15324},{"id":15347,"depth":729,"text":15348},{"id":15366,"depth":729,"text":15367},{"id":15388,"depth":729,"text":15389},{"id":15420,"depth":729,"text":15421,"children":16533},[16534,16535,16536,16537],{"id":15440,"depth":1112,"text":15441},{"id":15555,"depth":1112,"text":15556},{"id":16011,"depth":1112,"text":16012},{"id":16139,"depth":1112,"text":16140},{"id":16177,"depth":729,"text":16178},{"id":16226,"depth":729,"text":16227},{"id":16372,"depth":729,"text":16373},{"id":16446,"depth":729,"text":16447},{"id":16498,"depth":729,"text":16499},"2026-02-20T00:00:00.000Z","A step-by-step guide to creating a Claude Code skill that validates DDEX ERN XML files against official schemas - catching metadata errors before they reach distributors.",[16546,16548,16551],{"question":15324,"answer":16547},"A Claude skill is a folder containing instructions (SKILL.md) that teaches Claude how to handle specific tasks or workflows. Skills are reusable across Claude.ai, Claude Code, and the API.",{"question":16549,"answer":16550},"What is DDEX ERN?","DDEX ERN (Electronic Release Notification) is the music industry standard XML format for communicating metadata about music releases between labels, distributors, and digital service providers like Spotify and Apple Music.",{"question":16552,"answer":16553},"Do I need an MCP server for this skill?","No. This is a standalone skill that uses Claude's built-in code execution capabilities. No MCP server or external integrations are required.",{"src":16555},"/images/blog/musictechlab_blog_building-a-claude-skill-for-ddex-validation-music-metadata.webp",{"enabled":738,"items":16557},[16558,16560,16562],{"text":16559,"icon":12409},"A Claude skill validates DDEX ERN XML files on demand with no external services required.",{"text":16561,"icon":2939},"Progressive disclosure loads domain knowledge only when needed, keeping token usage low.",{"text":16563,"icon":3847},"The skill catches common rejection causes like hyphenated ISRCs and missing territory codes.",{},{"title":16566,"description":16567},"Building a Claude Skill for DDEX Validation | MusicTech Lab","Learn how to build a Claude Code skill that validates DDEX ERN XML files against official schemas. Step-by-step tutorial for music tech developers.",[9763,14100,3857],"trud8uwiY6UslRodHfCJ1YY0qyq94qzUFd1IQcwab9M",{"id":16571,"title":112,"authors":16572,"badge":16575,"body":16576,"category":731,"client":723,"date":18767,"description":18768,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":18769,"keyTakeaways":18771,"meta":18781,"navigation":738,"path":113,"seo":18782,"status":723,"stem":114,"tags":18783,"teaser":723,"__hash__":18785},"posts/blog/music-data/building-a-custom-music-delivery-platform-on-the-revelator-api.md",[16573],{"name":834,"to":720,"avatar":16574},{"src":722},{"label":1745,"color":1746},{"type":725,"value":16577,"toc":18735},[16578,16585,16587,16591,16594,16648,16651,16653,16657,16660,16668,16676,16683,16685,16689,16692,16695,16699,16716,16719,16748,16752,16763,16766,16777,16781,16792,16801,16803,16807,16810,16927,16931,16995,16999,17051,17055,17058,17752,17765,17767,17771,17774,17777,17781,17784,17995,17999,18002,18176,18180,18186,18377,18399,18403,18409,18426,18433,18435,18439,18442,18450,18454,18457,18461,18464,18520,18523,18527,18530,18534,18537,18539,18543,18546,18566,18569,18623,18632,18634,18638,18641,18667,18676,18678,18682,18685,18688,18690,18694,18732],[842,16579,16580,16581,16584],{},"Music distribution has never been more accessible. Platforms like DistroKid, TuneCore, and CD Baby democratized access to DSPs (Digital Service Providers) for independent artists. But for companies operating as distributors - managing ",[846,16582,16583],{"href":77},"multiple labels, complex territory restrictions",", and custom royalty splits - the generic UI and rigid workflows of consumer-facing tools quickly become a bottleneck.",[4937,16586],{},[863,16588,16590],{"id":16589},"why-distributors-outgrow-off-the-shelf-platforms","Why Distributors Outgrow Off-the-Shelf Platforms",[842,16592,16593],{},"The friction points are predictable:",[871,16595,16596,16606],{},[874,16597,16598],{},[877,16599,16600,16603],{},[880,16601,16602],{},"Pain Point",[880,16604,16605],{},"What Happens",[887,16607,16608,16618,16628,16638],{},[877,16609,16610,16615],{},[892,16611,16612],{},[996,16613,16614],{},"Territory restrictions",[892,16616,16617],{},"Many platforms only support album-level territory settings, forcing workarounds when individual tracks have different rights across markets",[877,16619,16620,16625],{},[892,16621,16622],{},[996,16623,16624],{},"Metadata flexibility",[892,16626,16627],{},"Contributor roles, localized titles, and DSP-specific artist IDs often require manual overrides the UI doesn't expose",[877,16629,16630,16635],{},[892,16631,16632],{},[996,16633,16634],{},"Reporting",[892,16636,16637],{},"Off-the-shelf dashboards rarely match the operational needs of a multi-label distributor",[877,16639,16640,16645],{},[892,16641,16642],{},[996,16643,16644],{},"Delivery control",[892,16646,16647],{},"When a platform handles delivery as a black box, debugging ingestion failures becomes a support ticket game",[842,16649,16650],{},"This is where the build-vs-buy decision becomes real.",[4937,16652],{},[863,16654,16656],{"id":16655},"the-two-paths-frontend-only-vs-full-stack","The Two Paths: Frontend-Only vs. Full Stack",[842,16658,16659],{},"Distributors who've outgrown their current setup face a fundamental architectural choice:",[1572,16661,16662],{},[842,16663,16664,16667],{},[996,16665,16666],{},"Option A: Custom Frontend + Revelator API as Backend","\nBuild your own user-facing layer - UX, workflows, internal tools, reporting - and use Revelator's API purely for catalog management, delivery to DSPs, and royalty ingestion.",[1901,16669,16670],{},[842,16671,16672,16675],{},[996,16673,16674],{},"Option B: Full-Stack Distribution Platform","\nReplace the platform entirely. Build metadata management, DDEX (Digital Data Exchange) generation, delivery pipelines, DSP integrations, royalty ingestion, reporting, and payouts from scratch.",[842,16677,16678,16679,16682],{},"This article focuses on ",[996,16680,16681],{},"Option A",": what it looks like in practice, where it excels, and where it hits structural limits.",[4937,16684],{},[863,16686,16688],{"id":16687},"what-the-revelator-api-actually-offers","What the Revelator API Actually Offers",[842,16690,16691],{},"Revelator positions itself as an \"end-to-end operating system\" for independent music businesses. Unlike consumer-facing distributors, it's B2B infrastructure - designed for labels, aggregators, and distributors who want to run their own branded operation.",[842,16693,16694],{},"The API is RESTful, JSON-based, and covers five core modules:",[1074,16696,16698],{"id":16697},"catalog-management","Catalog Management",[958,16700,16701,16707,16710,16713],{},[961,16702,16703,16704],{},"Create and edit releases via ",[895,16705,16706],{},"POST /content/release/save",[961,16708,16709],{},"Upload audio (WAV/FLAC, minimum 16-bit, 44.1 kHz stereo) and cover art (minimum 1400x1400px JPG/RGB)",[961,16711,16712],{},"Manage ISRCs (International Standard Recording Codes), UPCs (Universal Product Codes), and external DSP artist IDs",[961,16714,16715],{},"Support for multi-disc releases and localized metadata",[1074,16717,1745],{"id":16718},"distribution",[958,16720,16721,16727,16730,16736,16739,16745],{},[961,16722,16723,16724],{},"Pre-delivery validation: ",[895,16725,16726],{},"POST /distribution/release/{releaseId}/validate",[961,16728,16729],{},"Set DSP targets, release dates, and territories per release",[961,16731,16732,16733],{},"Queue releases for delivery: ",[895,16734,16735],{},"POST /distribution/release/addtoqueue",[961,16737,16738],{},"Track delivery status (statuses range from -20 to 100; 50+ means delivered)",[961,16740,16741,16742],{},"Takedown support: ",[895,16743,16744],{},"POST /distribution/release/takedown",[961,16746,16747],{},"Webhook callbacks for delivery status updates",[1074,16749,16751],{"id":16750},"rights-and-royalties","Rights and Royalties",[958,16753,16754,16757,16760],{},[961,16755,16756],{},"Contract definition with configurable splits and recoupables",[961,16758,16759],{},"DSP statement import and reconciliation",[961,16761,16762],{},"Multi-currency payout automation via Tipalti and PayPal",[1074,16764,16765],{"id":3191},"Analytics",[958,16767,16768,16771,16774],{},[961,16769,16770],{},"Streaming and revenue data queryable by track, release, region, or DSP",[961,16772,16773],{},"Playlist performance tracking",[961,16775,16776],{},"CSV export and raw data sync",[1074,16778,16780],{"id":16779},"account-management","Account Management",[958,16782,16783,16786,16789],{},[961,16784,16785],{},"Parent-child account hierarchy (your enterprise is the parent; each label or artist is a child)",[961,16787,16788],{},"Full visibility into child account assets",[961,16790,16791],{},"Mandatory approval step before content reaches DSPs",[1032,16793,16794],{},[842,16795,16796,16797,16800],{},"Revelator claims ",[996,16798,16799],{},"100+ DSP integrations",", including Spotify, Apple Music, Amazon, YouTube Music, YouTube Content ID, TikTok, and Deezer. DSP access is configurable per child account.",[4937,16802],{},[863,16804,16806],{"id":16805},"architecture-of-the-hybrid-solution","Architecture of the Hybrid Solution",[842,16808,16809],{},"The hybrid approach layers your custom platform on top of Revelator's delivery infrastructure:",[1013,16811,16813],{"className":3341,"code":16812,"language":3343,"meta":728,"style":728},"graph TD\n    subgraph Frontend[Your Custom Frontend]\n        AP[Artist Portal]\n        LD[Label Dashboard]\n        AT[Admin Tools]\n        AP & LD & AT --> BE[Your API / Backend]\n    end\n    subgraph Revelator[Revelator API Layer]\n        CAT[Catalog Mgmt API]\n        DEL[Delivery API]\n        ROY[Royalty Ingestion]\n    end\n    BE --> CAT & DEL & ROY\n    subgraph DSPs[Digital Service Providers]\n        SP[Spotify]\n        AM[Apple Music]\n        YT[YouTube]\n        MORE[100+ more]\n    end\n    DEL --> SP & AM & YT & MORE\n    style Frontend fill:#0f172a,stroke:#38bdf8,color:#f8fafc\n    style Revelator fill:#1e293b,stroke:#0ea5e9,color:#f8fafc\n    style DSPs fill:#0f172a,stroke:#334155,color:#f8fafc\n",[895,16814,16815,16820,16825,16830,16835,16840,16845,16849,16854,16859,16864,16869,16873,16878,16883,16888,16893,16898,16903,16907,16912,16917,16922],{"__ignoreMap":728},[1086,16816,16817],{"class":1088,"line":1089},[1086,16818,16819],{"class":1436},"graph TD\n",[1086,16821,16822],{"class":1088,"line":729},[1086,16823,16824],{"class":1436},"    subgraph Frontend[Your Custom Frontend]\n",[1086,16826,16827],{"class":1088,"line":1112},[1086,16828,16829],{"class":1436},"        AP[Artist Portal]\n",[1086,16831,16832],{"class":1088,"line":1181},[1086,16833,16834],{"class":1436},"        LD[Label Dashboard]\n",[1086,16836,16837],{"class":1088,"line":1205},[1086,16838,16839],{"class":1436},"        AT[Admin Tools]\n",[1086,16841,16842],{"class":1088,"line":1276},[1086,16843,16844],{"class":1436},"        AP & LD & AT --> BE[Your API / Backend]\n",[1086,16846,16847],{"class":1088,"line":1282},[1086,16848,13084],{"class":1436},[1086,16850,16851],{"class":1088,"line":1288},[1086,16852,16853],{"class":1436},"    subgraph Revelator[Revelator API Layer]\n",[1086,16855,16856],{"class":1088,"line":2685},[1086,16857,16858],{"class":1436},"        CAT[Catalog Mgmt API]\n",[1086,16860,16861],{"class":1088,"line":2700},[1086,16862,16863],{"class":1436},"        DEL[Delivery API]\n",[1086,16865,16866],{"class":1088,"line":3398},[1086,16867,16868],{"class":1436},"        ROY[Royalty Ingestion]\n",[1086,16870,16871],{"class":1088,"line":1715},[1086,16872,13084],{"class":1436},[1086,16874,16875],{"class":1088,"line":3409},[1086,16876,16877],{"class":1436},"    BE --> CAT & DEL & ROY\n",[1086,16879,16880],{"class":1088,"line":3415},[1086,16881,16882],{"class":1436},"    subgraph DSPs[Digital Service Providers]\n",[1086,16884,16885],{"class":1088,"line":3421},[1086,16886,16887],{"class":1436},"        SP[Spotify]\n",[1086,16889,16890],{"class":1088,"line":3427},[1086,16891,16892],{"class":1436},"        AM[Apple Music]\n",[1086,16894,16895],{"class":1088,"line":3433},[1086,16896,16897],{"class":1436},"        YT[YouTube]\n",[1086,16899,16900],{"class":1088,"line":3439},[1086,16901,16902],{"class":1436},"        MORE[100+ more]\n",[1086,16904,16905],{"class":1088,"line":3444},[1086,16906,13084],{"class":1436},[1086,16908,16909],{"class":1088,"line":3450},[1086,16910,16911],{"class":1436},"    DEL --> SP & AM & YT & MORE\n",[1086,16913,16914],{"class":1088,"line":3456},[1086,16915,16916],{"class":1436},"    style Frontend fill:#0f172a,stroke:#38bdf8,color:#f8fafc\n",[1086,16918,16919],{"class":1088,"line":3462},[1086,16920,16921],{"class":1436},"    style Revelator fill:#1e293b,stroke:#0ea5e9,color:#f8fafc\n",[1086,16923,16924],{"class":1088,"line":3467},[1086,16925,16926],{"class":1436},"    style DSPs fill:#0f172a,stroke:#334155,color:#f8fafc\n",[1074,16928,16930],{"id":16929},"what-you-build","What You Build",[871,16932,16933,16943],{},[874,16934,16935],{},[877,16936,16937,16940],{},[880,16938,16939],{},"Layer",[880,16941,16942],{},"Responsibility",[887,16944,16945,16955,16965,16975,16985],{},[877,16946,16947,16952],{},[892,16948,16949],{},[996,16950,16951],{},"Artist & label portals",[892,16953,16954],{},"Branded onboarding, release submission, approval workflows",[877,16956,16957,16962],{},[892,16958,16959],{},[996,16960,16961],{},"Metadata layer",[892,16963,16964],{},"Your own database of releases, tracks, contributors, and rights - synced to Revelator via API",[877,16966,16967,16972],{},[892,16968,16969],{},[996,16970,16971],{},"Territory & rights engine",[892,16973,16974],{},"Business logic for track-level territory restrictions, split sheets, and embargo rules",[877,16976,16977,16982],{},[892,16978,16979],{},[996,16980,16981],{},"Reporting dashboards",[892,16983,16984],{},"Custom views pulling from Revelator's analytics API plus your own data",[877,16986,16987,16992],{},[892,16988,16989],{},[996,16990,16991],{},"Internal tools",[892,16993,16994],{},"Approval queues, QC checklists, bulk operations, CRM integration",[1074,16996,16998],{"id":16997},"what-revelator-handles","What Revelator Handles",[871,17000,17001,17009],{},[874,17002,17003],{},[877,17004,17005,17007],{},[880,17006,16939],{},[880,17008,16942],{},[887,17010,17011,17021,17031,17041],{},[877,17012,17013,17018],{},[892,17014,17015],{},[996,17016,17017],{},"DDEX generation",[892,17019,17020],{},"Generates ERN (Electronic Release Notification) XML packages from catalog data you push via API",[877,17022,17023,17028],{},[892,17024,17025],{},[996,17026,17027],{},"DSP delivery",[892,17029,17030],{},"SFTP/API delivery to all connected DSPs, including retry logic and status tracking",[877,17032,17033,17038],{},[892,17034,17035],{},[996,17036,17037],{},"Royalty ingestion",[892,17039,17040],{},"Parsing DSP statements and normalizing revenue data",[877,17042,17043,17048],{},[892,17044,17045],{},[996,17046,17047],{},"Payout infrastructure",[892,17049,17050],{},"Payment rail integrations for artist payouts",[1074,17052,17054],{"id":17053},"quick-example-creating-a-release-via-revelator-api","Quick Example: Creating a Release via Revelator API",[842,17056,17057],{},"Here's a minimal example of how your backend would create and distribute a release through Revelator's API:",[1013,17059,17061],{"className":1368,"code":17060,"language":1250,"meta":728,"style":728},"import httpx\n\nREVELATOR_API = \"https://api.revelator.com\"\n\n# 1. Authenticate\nauth = httpx.post(f\"{REVELATOR_API}/partner/account/login\", json={\n    \"email\": \"your@email.com\",\n    \"password\": \"your-password\"\n})\ntoken = auth.json()[\"token\"]\nheaders = {\"Authorization\": f\"Bearer {token}\"}\n\n# 2. Create a release\nrelease = httpx.post(f\"{REVELATOR_API}/content/release/save\", headers=headers, json={\n    \"title\": \"Delilah - Summer Version\",\n    \"releaseType\": \"Single\",\n    \"artists\": [\n        {\"name\": \"MIKOLAS\", \"role\": \"MainArtist\"},\n        {\"name\": \"Mark Neve\", \"role\": \"MainArtist\"}\n    ],\n    \"genre\": \"Pop\",\n    \"releaseDate\": \"2026-04-01\",\n    \"territories\": {\n        \"include\": \"Worldwide\",\n        \"exclude\": [\"JP\", \"DE\", \"AT\", \"CH\", \"NL\", \"AU\", \"NZ\"]\n    }\n})\nrelease_id = release.json()[\"id\"]\n\n# 3. Validate before delivery\nvalidation = httpx.post(\n    f\"{REVELATOR_API}/distribution/release/{release_id}/validate\",\n    headers=headers\n)\n\n# 4. Queue for delivery to DSPs\nif validation.json()[\"valid\"]:\n    httpx.post(f\"{REVELATOR_API}/distribution/release/addtoqueue\",\n        headers=headers,\n        json={\"releaseId\": release_id}\n    )\n",[895,17062,17063,17070,17074,17088,17092,17097,17132,17152,17169,17173,17198,17231,17235,17240,17284,17303,17323,17336,17376,17413,17418,17437,17457,17469,17488,17564,17568,17572,17596,17600,17605,17619,17646,17656,17660,17664,17669,17689,17715,17726,17747],{"__ignoreMap":728},[1086,17064,17065,17067],{"class":1088,"line":1089},[1086,17066,6503],{"class":1423},[1086,17068,17069],{"class":1436}," httpx\n",[1086,17071,17072],{"class":1088,"line":729},[1086,17073,3390],{"emptyLinePlaceholder":738},[1086,17075,17076,17079,17081,17083,17086],{"class":1088,"line":1112},[1086,17077,17078],{"class":1436},"REVELATOR_API ",[1086,17080,1440],{"class":1146},[1086,17082,1195],{"class":1146},[1086,17084,17085],{"class":1096},"https://api.revelator.com",[1086,17087,4441],{"class":1146},[1086,17089,17090],{"class":1088,"line":1181},[1086,17091,3390],{"emptyLinePlaceholder":738},[1086,17093,17094],{"class":1088,"line":1205},[1086,17095,17096],{"class":1427},"# 1. Authenticate\n",[1086,17098,17099,17102,17104,17106,17108,17110,17112,17114,17116,17118,17121,17123,17126,17128,17130],{"class":1088,"line":1276},[1086,17100,17101],{"class":1436},"auth ",[1086,17103,1440],{"class":1146},[1086,17105,7160],{"class":1436},[1086,17107,861],{"class":1146},[1086,17109,7165],{"class":1105},[1086,17111,1398],{"class":1146},[1086,17113,5962],{"class":1155},[1086,17115,1159],{"class":1096},[1086,17117,4409],{"class":1187},[1086,17119,17120],{"class":1105},"REVELATOR_API",[1086,17122,4423],{"class":1187},[1086,17124,17125],{"class":1096},"/partner/account/login\"",[1086,17127,1227],{"class":1146},[1086,17129,1463],{"class":1401},[1086,17131,7203],{"class":1146},[1086,17133,17134,17136,17139,17141,17143,17145,17148,17150],{"class":1088,"line":1282},[1086,17135,1169],{"class":1146},[1086,17137,17138],{"class":1096},"email",[1086,17140,1159],{"class":1146},[1086,17142,1133],{"class":1146},[1086,17144,1195],{"class":1146},[1086,17146,17147],{"class":1096},"your@email.com",[1086,17149,1159],{"class":1146},[1086,17151,1202],{"class":1146},[1086,17153,17154,17156,17159,17161,17163,17165,17167],{"class":1088,"line":1288},[1086,17155,1169],{"class":1146},[1086,17157,17158],{"class":1096},"password",[1086,17160,1159],{"class":1146},[1086,17162,1133],{"class":1146},[1086,17164,1195],{"class":1146},[1086,17166,12194],{"class":1096},[1086,17168,4441],{"class":1146},[1086,17170,17171],{"class":1088,"line":2685},[1086,17172,1567],{"class":1146},[1086,17174,17175,17178,17180,17183,17185,17187,17190,17192,17194,17196],{"class":1088,"line":2700},[1086,17176,17177],{"class":1436},"token ",[1086,17179,1440],{"class":1146},[1086,17181,17182],{"class":1436}," auth",[1086,17184,861],{"class":1146},[1086,17186,1139],{"class":1105},[1086,17188,17189],{"class":1146},"()[",[1086,17191,1159],{"class":1146},[1086,17193,4249],{"class":1096},[1086,17195,1159],{"class":1146},[1086,17197,1273],{"class":1146},[1086,17199,17200,17203,17205,17207,17209,17212,17214,17216,17218,17221,17223,17225,17227,17229],{"class":1088,"line":3398},[1086,17201,17202],{"class":1436},"headers ",[1086,17204,1440],{"class":1146},[1086,17206,4520],{"class":1146},[1086,17208,1159],{"class":1146},[1086,17210,17211],{"class":1096},"Authorization",[1086,17213,1159],{"class":1146},[1086,17215,1133],{"class":1146},[1086,17217,4403],{"class":1155},[1086,17219,17220],{"class":1096},"\"Bearer ",[1086,17222,4409],{"class":1187},[1086,17224,4249],{"class":1436},[1086,17226,4423],{"class":1187},[1086,17228,1159],{"class":1096},[1086,17230,1291],{"class":1146},[1086,17232,17233],{"class":1088,"line":1715},[1086,17234,3390],{"emptyLinePlaceholder":738},[1086,17236,17237],{"class":1088,"line":3409},[1086,17238,17239],{"class":1427},"# 2. Create a release\n",[1086,17241,17242,17245,17247,17249,17251,17253,17255,17257,17259,17261,17263,17265,17268,17270,17273,17275,17278,17280,17282],{"class":1088,"line":3415},[1086,17243,17244],{"class":1436},"release ",[1086,17246,1440],{"class":1146},[1086,17248,7160],{"class":1436},[1086,17250,861],{"class":1146},[1086,17252,7165],{"class":1105},[1086,17254,1398],{"class":1146},[1086,17256,5962],{"class":1155},[1086,17258,1159],{"class":1096},[1086,17260,4409],{"class":1187},[1086,17262,17120],{"class":1105},[1086,17264,4423],{"class":1187},[1086,17266,17267],{"class":1096},"/content/release/save\"",[1086,17269,1227],{"class":1146},[1086,17271,17272],{"class":1401}," headers",[1086,17274,1440],{"class":1146},[1086,17276,17277],{"class":1105},"headers",[1086,17279,1227],{"class":1146},[1086,17281,1463],{"class":1401},[1086,17283,7203],{"class":1146},[1086,17285,17286,17288,17290,17292,17294,17296,17299,17301],{"class":1088,"line":3421},[1086,17287,1169],{"class":1146},[1086,17289,9069],{"class":1096},[1086,17291,1159],{"class":1146},[1086,17293,1133],{"class":1146},[1086,17295,1195],{"class":1146},[1086,17297,17298],{"class":1096},"Delilah - Summer Version",[1086,17300,1159],{"class":1146},[1086,17302,1202],{"class":1146},[1086,17304,17305,17307,17310,17312,17314,17316,17319,17321],{"class":1088,"line":3427},[1086,17306,1169],{"class":1146},[1086,17308,17309],{"class":1096},"releaseType",[1086,17311,1159],{"class":1146},[1086,17313,1133],{"class":1146},[1086,17315,1195],{"class":1146},[1086,17317,17318],{"class":1096},"Single",[1086,17320,1159],{"class":1146},[1086,17322,1202],{"class":1146},[1086,17324,17325,17327,17330,17332,17334],{"class":1088,"line":3433},[1086,17326,1169],{"class":1146},[1086,17328,17329],{"class":1096},"artists",[1086,17331,1159],{"class":1146},[1086,17333,1133],{"class":1146},[1086,17335,6580],{"class":1146},[1086,17337,17338,17341,17343,17345,17347,17349,17351,17354,17356,17358,17360,17363,17365,17367,17369,17371,17373],{"class":1088,"line":3439},[1086,17339,17340],{"class":1146},"        {",[1086,17342,1159],{"class":1146},[1086,17344,4184],{"class":1096},[1086,17346,1159],{"class":1146},[1086,17348,1133],{"class":1146},[1086,17350,1195],{"class":1146},[1086,17352,17353],{"class":1096},"MIKOLAS",[1086,17355,1159],{"class":1146},[1086,17357,1227],{"class":1146},[1086,17359,1195],{"class":1146},[1086,17361,17362],{"class":1096},"role",[1086,17364,1159],{"class":1146},[1086,17366,1133],{"class":1146},[1086,17368,1195],{"class":1146},[1086,17370,11328],{"class":1096},[1086,17372,1159],{"class":1146},[1086,17374,17375],{"class":1146},"},\n",[1086,17377,17378,17380,17382,17384,17386,17388,17390,17393,17395,17397,17399,17401,17403,17405,17407,17409,17411],{"class":1088,"line":3444},[1086,17379,17340],{"class":1146},[1086,17381,1159],{"class":1146},[1086,17383,4184],{"class":1096},[1086,17385,1159],{"class":1146},[1086,17387,1133],{"class":1146},[1086,17389,1195],{"class":1146},[1086,17391,17392],{"class":1096},"Mark Neve",[1086,17394,1159],{"class":1146},[1086,17396,1227],{"class":1146},[1086,17398,1195],{"class":1146},[1086,17400,17362],{"class":1096},[1086,17402,1159],{"class":1146},[1086,17404,1133],{"class":1146},[1086,17406,1195],{"class":1146},[1086,17408,11328],{"class":1096},[1086,17410,1159],{"class":1146},[1086,17412,1291],{"class":1146},[1086,17414,17415],{"class":1088,"line":3450},[1086,17416,17417],{"class":1146},"    ],\n",[1086,17419,17420,17422,17424,17426,17428,17430,17433,17435],{"class":1088,"line":3456},[1086,17421,1169],{"class":1146},[1086,17423,9108],{"class":1096},[1086,17425,1159],{"class":1146},[1086,17427,1133],{"class":1146},[1086,17429,1195],{"class":1146},[1086,17431,17432],{"class":1096},"Pop",[1086,17434,1159],{"class":1146},[1086,17436,1202],{"class":1146},[1086,17438,17439,17441,17444,17446,17448,17450,17453,17455],{"class":1088,"line":3462},[1086,17440,1169],{"class":1146},[1086,17442,17443],{"class":1096},"releaseDate",[1086,17445,1159],{"class":1146},[1086,17447,1133],{"class":1146},[1086,17449,1195],{"class":1146},[1086,17451,17452],{"class":1096},"2026-04-01",[1086,17454,1159],{"class":1146},[1086,17456,1202],{"class":1146},[1086,17458,17459,17461,17463,17465,17467],{"class":1088,"line":3467},[1086,17460,1169],{"class":1146},[1086,17462,13298],{"class":1096},[1086,17464,1159],{"class":1146},[1086,17466,1133],{"class":1146},[1086,17468,1164],{"class":1146},[1086,17470,17471,17473,17476,17478,17480,17482,17484,17486],{"class":1088,"line":3473},[1086,17472,6046],{"class":1146},[1086,17474,17475],{"class":1096},"include",[1086,17477,1159],{"class":1146},[1086,17479,1133],{"class":1146},[1086,17481,1195],{"class":1146},[1086,17483,10398],{"class":1096},[1086,17485,1159],{"class":1146},[1086,17487,1202],{"class":1146},[1086,17489,17490,17492,17495,17497,17499,17501,17503,17506,17508,17510,17512,17515,17517,17519,17521,17524,17526,17528,17530,17533,17535,17537,17539,17542,17544,17546,17548,17551,17553,17555,17557,17560,17562],{"class":1088,"line":3479},[1086,17491,6046],{"class":1146},[1086,17493,17494],{"class":1096},"exclude",[1086,17496,1159],{"class":1146},[1086,17498,1133],{"class":1146},[1086,17500,1217],{"class":1146},[1086,17502,1159],{"class":1146},[1086,17504,17505],{"class":1096},"JP",[1086,17507,1159],{"class":1146},[1086,17509,1227],{"class":1146},[1086,17511,1195],{"class":1146},[1086,17513,17514],{"class":1096},"DE",[1086,17516,1159],{"class":1146},[1086,17518,1227],{"class":1146},[1086,17520,1195],{"class":1146},[1086,17522,17523],{"class":1096},"AT",[1086,17525,1159],{"class":1146},[1086,17527,1227],{"class":1146},[1086,17529,1195],{"class":1146},[1086,17531,17532],{"class":1096},"CH",[1086,17534,1159],{"class":1146},[1086,17536,1227],{"class":1146},[1086,17538,1195],{"class":1146},[1086,17540,17541],{"class":1096},"NL",[1086,17543,1159],{"class":1146},[1086,17545,1227],{"class":1146},[1086,17547,1195],{"class":1146},[1086,17549,17550],{"class":1096},"AU",[1086,17552,1159],{"class":1146},[1086,17554,1227],{"class":1146},[1086,17556,1195],{"class":1146},[1086,17558,17559],{"class":1096},"NZ",[1086,17561,1159],{"class":1146},[1086,17563,1273],{"class":1146},[1086,17565,17566],{"class":1088,"line":3485},[1086,17567,1279],{"class":1146},[1086,17569,17570],{"class":1088,"line":3491},[1086,17571,1567],{"class":1146},[1086,17573,17574,17577,17579,17582,17584,17586,17588,17590,17592,17594],{"class":1088,"line":3497},[1086,17575,17576],{"class":1436},"release_id ",[1086,17578,1440],{"class":1146},[1086,17580,17581],{"class":1436}," release",[1086,17583,861],{"class":1146},[1086,17585,1139],{"class":1105},[1086,17587,17189],{"class":1146},[1086,17589,1159],{"class":1146},[1086,17591,4156],{"class":1096},[1086,17593,1159],{"class":1146},[1086,17595,1273],{"class":1146},[1086,17597,17598],{"class":1088,"line":3503},[1086,17599,3390],{"emptyLinePlaceholder":738},[1086,17601,17602],{"class":1088,"line":3509},[1086,17603,17604],{"class":1427},"# 3. Validate before delivery\n",[1086,17606,17607,17609,17611,17613,17615,17617],{"class":1088,"line":3515},[1086,17608,11042],{"class":1436},[1086,17610,1440],{"class":1146},[1086,17612,7160],{"class":1436},[1086,17614,861],{"class":1146},[1086,17616,7165],{"class":1105},[1086,17618,4094],{"class":1146},[1086,17620,17621,17623,17625,17627,17629,17631,17634,17636,17639,17641,17644],{"class":1088,"line":3520},[1086,17622,7172],{"class":1155},[1086,17624,1159],{"class":1096},[1086,17626,4409],{"class":1187},[1086,17628,17120],{"class":1105},[1086,17630,4423],{"class":1187},[1086,17632,17633],{"class":1096},"/distribution/release/",[1086,17635,4409],{"class":1187},[1086,17637,17638],{"class":1105},"release_id",[1086,17640,4423],{"class":1187},[1086,17642,17643],{"class":1096},"/validate\"",[1086,17645,1202],{"class":1146},[1086,17647,17648,17651,17653],{"class":1088,"line":3526},[1086,17649,17650],{"class":1401},"    headers",[1086,17652,1440],{"class":1146},[1086,17654,17655],{"class":1105},"headers\n",[1086,17657,17658],{"class":1088,"line":3531},[1086,17659,1455],{"class":1146},[1086,17661,17662],{"class":1088,"line":3537},[1086,17663,3390],{"emptyLinePlaceholder":738},[1086,17665,17666],{"class":1088,"line":3543},[1086,17667,17668],{"class":1427},"# 4. Queue for delivery to DSPs\n",[1086,17670,17671,17673,17675,17677,17679,17681,17683,17685,17687],{"class":1088,"line":3549},[1086,17672,11056],{"class":1423},[1086,17674,11061],{"class":1436},[1086,17676,861],{"class":1146},[1086,17678,1139],{"class":1105},[1086,17680,17189],{"class":1146},[1086,17682,1159],{"class":1146},[1086,17684,15974],{"class":1096},[1086,17686,1159],{"class":1146},[1086,17688,10888],{"class":1146},[1086,17690,17691,17694,17696,17698,17700,17702,17704,17706,17708,17710,17713],{"class":1088,"line":3555},[1086,17692,17693],{"class":1436},"    httpx",[1086,17695,861],{"class":1146},[1086,17697,7165],{"class":1105},[1086,17699,1398],{"class":1146},[1086,17701,5962],{"class":1155},[1086,17703,1159],{"class":1096},[1086,17705,4409],{"class":1187},[1086,17707,17120],{"class":1105},[1086,17709,4423],{"class":1187},[1086,17711,17712],{"class":1096},"/distribution/release/addtoqueue\"",[1086,17714,1202],{"class":1146},[1086,17716,17717,17720,17722,17724],{"class":1088,"line":3561},[1086,17718,17719],{"class":1401},"        headers",[1086,17721,1440],{"class":1146},[1086,17723,17277],{"class":1105},[1086,17725,1202],{"class":1146},[1086,17727,17728,17731,17733,17735,17738,17740,17742,17745],{"class":1088,"line":3567},[1086,17729,17730],{"class":1401},"        json",[1086,17732,1553],{"class":1146},[1086,17734,1159],{"class":1146},[1086,17736,17737],{"class":1096},"releaseId",[1086,17739,1159],{"class":1146},[1086,17741,1133],{"class":1146},[1086,17743,17744],{"class":1105}," release_id",[1086,17746,1291],{"class":1146},[1086,17748,17750],{"class":1088,"line":17749},41,[1086,17751,6219],{"class":1146},[1572,17753,17754],{},[842,17755,17756,17757,17760,17761,17764],{},"This is a simplified example. In production, you'd also upload audio files via ",[895,17758,17759],{},"/media/audio/upload",", cover art via ",[895,17762,17763],{},"/media/image/upload",", and handle webhook callbacks for delivery status updates.",[4937,17766],{},[863,17768,17770],{"id":17769},"ddex-as-the-backbone","DDEX as the Backbone",[842,17772,17773],{},"Under the hood, every delivery to a DSP is a DDEX ERN message - an XML package describing the release, its resources (audio, artwork), and the commercial terms (deals) under which DSPs can exploit the content.",[842,17775,17776],{},"An ERN message has three critical sections:",[1074,17778,17780],{"id":17779},"resourcelist","ResourceList",[842,17782,17783],{},"Defines the audio files, cover art, and their metadata. Each sound recording includes an ISRC, title, artist credits, and territory-specific details:",[1013,17785,17787],{"className":11154,"code":17786,"language":11157,"meta":728,"style":728},"\u003CSoundRecording>\n  \u003CSoundRecordingId>\n    \u003CISRC>SKXXX2500001\u003C/ISRC>\n  \u003C/SoundRecordingId>\n  \u003CResourceReference>A1\u003C/ResourceReference>\n  \u003CSoundRecordingDetailsByTerritory>\n    \u003CTerritoryCode>Worldwide\u003C/TerritoryCode>\n    \u003CTitle TitleType=\"FormalTitle\">\n      \u003CTitleText>Delilah\u003C/TitleText>\n    \u003C/Title>\n    \u003CDisplayArtist>\n      \u003CPartyName>\u003CFullName>MIKOLAS\u003C/FullName>\u003C/PartyName>\n      \u003CArtistRole>MainArtist\u003C/ArtistRole>\n    \u003C/DisplayArtist>\n  \u003C/SoundRecordingDetailsByTerritory>\n\u003C/SoundRecording>\n",[895,17788,17789,17797,17805,17822,17830,17846,17855,17871,17891,17910,17919,17927,17955,17971,17979,17987],{"__ignoreMap":728},[1086,17790,17791,17793,17795],{"class":1088,"line":1089},[1086,17792,11164],{"class":1146},[1086,17794,11280],{"class":4109},[1086,17796,11170],{"class":1146},[1086,17798,17799,17801,17803],{"class":1088,"line":729},[1086,17800,11180],{"class":1146},[1086,17802,16266],{"class":4109},[1086,17804,11170],{"class":1146},[1086,17806,17807,17809,17811,17813,17816,17818,17820],{"class":1088,"line":1112},[1086,17808,11190],{"class":1146},[1086,17810,3856],{"class":4109},[1086,17812,2694],{"class":1146},[1086,17814,17815],{"class":1436},"SKXXX2500001",[1086,17817,11201],{"class":1146},[1086,17819,3856],{"class":4109},[1086,17821,11170],{"class":1146},[1086,17823,17824,17826,17828],{"class":1088,"line":1181},[1086,17825,11260],{"class":1146},[1086,17827,16266],{"class":4109},[1086,17829,11170],{"class":1146},[1086,17831,17832,17834,17836,17838,17840,17842,17844],{"class":1088,"line":1205},[1086,17833,11180],{"class":1146},[1086,17835,16099],{"class":4109},[1086,17837,2694],{"class":1146},[1086,17839,16104],{"class":1436},[1086,17841,11201],{"class":1146},[1086,17843,16099],{"class":4109},[1086,17845,11170],{"class":1146},[1086,17847,17848,17850,17853],{"class":1088,"line":1276},[1086,17849,11180],{"class":1146},[1086,17851,17852],{"class":4109},"SoundRecordingDetailsByTerritory",[1086,17854,11170],{"class":1146},[1086,17856,17857,17859,17861,17863,17865,17867,17869],{"class":1088,"line":1282},[1086,17858,11190],{"class":1146},[1086,17860,16322],{"class":4109},[1086,17862,2694],{"class":1146},[1086,17864,10398],{"class":1436},[1086,17866,11201],{"class":1146},[1086,17868,16322],{"class":4109},[1086,17870,11170],{"class":1146},[1086,17872,17873,17875,17877,17880,17882,17884,17887,17889],{"class":1088,"line":1288},[1086,17874,11190],{"class":1146},[1086,17876,8859],{"class":4109},[1086,17878,17879],{"class":1155}," TitleType",[1086,17881,1440],{"class":1146},[1086,17883,1159],{"class":1146},[1086,17885,17886],{"class":1096},"FormalTitle",[1086,17888,1159],{"class":1146},[1086,17890,11170],{"class":1146},[1086,17892,17893,17896,17899,17901,17904,17906,17908],{"class":1088,"line":2685},[1086,17894,17895],{"class":1146},"      \u003C",[1086,17897,17898],{"class":4109},"TitleText",[1086,17900,2694],{"class":1146},[1086,17902,17903],{"class":1436},"Delilah",[1086,17905,11201],{"class":1146},[1086,17907,17898],{"class":4109},[1086,17909,11170],{"class":1146},[1086,17911,17912,17915,17917],{"class":1088,"line":2700},[1086,17913,17914],{"class":1146},"    \u003C/",[1086,17916,8859],{"class":4109},[1086,17918,11170],{"class":1146},[1086,17920,17921,17923,17925],{"class":1088,"line":3398},[1086,17922,11190],{"class":1146},[1086,17924,10500],{"class":4109},[1086,17926,11170],{"class":1146},[1086,17928,17929,17931,17934,17937,17940,17942,17944,17946,17948,17951,17953],{"class":1088,"line":1715},[1086,17930,17895],{"class":1146},[1086,17932,17933],{"class":4109},"PartyName",[1086,17935,17936],{"class":1146},">\u003C",[1086,17938,17939],{"class":4109},"FullName",[1086,17941,2694],{"class":1146},[1086,17943,17353],{"class":1436},[1086,17945,11201],{"class":1146},[1086,17947,17939],{"class":4109},[1086,17949,17950],{"class":1146},">\u003C/",[1086,17952,17933],{"class":4109},[1086,17954,11170],{"class":1146},[1086,17956,17957,17959,17961,17963,17965,17967,17969],{"class":1088,"line":3409},[1086,17958,17895],{"class":1146},[1086,17960,11323],{"class":4109},[1086,17962,2694],{"class":1146},[1086,17964,11328],{"class":1436},[1086,17966,11201],{"class":1146},[1086,17968,11323],{"class":4109},[1086,17970,11170],{"class":1146},[1086,17972,17973,17975,17977],{"class":1088,"line":3415},[1086,17974,17914],{"class":1146},[1086,17976,10500],{"class":4109},[1086,17978,11170],{"class":1146},[1086,17980,17981,17983,17985],{"class":1088,"line":3421},[1086,17982,11260],{"class":1146},[1086,17984,17852],{"class":4109},[1086,17986,11170],{"class":1146},[1086,17988,17989,17991,17993],{"class":1088,"line":3427},[1086,17990,11201],{"class":1146},[1086,17992,11280],{"class":4109},[1086,17994,11170],{"class":1146},[1074,17996,17998],{"id":17997},"releaselist","ReleaseList",[842,18000,18001],{},"Defines the product - the album or single - and links it to its component resources:",[1013,18003,18005],{"className":11154,"code":18004,"language":11157,"meta":728,"style":728},"\u003CRelease>\n  \u003CReleaseId>\u003CICPN>0123456789012\u003C/ICPN>\u003C/ReleaseId>\n  \u003CReleaseReference>R0\u003C/ReleaseReference>\n  \u003CReleaseType>Album\u003C/ReleaseType>\n  \u003CReleaseDetailsByTerritory>\n    \u003CTerritoryCode>Worldwide\u003C/TerritoryCode>\n    \u003CDisplayArtistName>MIKOLAS\u003C/DisplayArtistName>\n    \u003CTitle TitleType=\"FormalTitle\">\n      \u003CTitleText>ONE\u003C/TitleText>\n    \u003C/Title>\n  \u003C/ReleaseDetailsByTerritory>\n\u003C/Release>\n",[895,18006,18007,18015,18040,18058,18075,18084,18100,18117,18135,18152,18160,18168],{"__ignoreMap":728},[1086,18008,18009,18011,18013],{"class":1088,"line":1089},[1086,18010,11164],{"class":1146},[1086,18012,11183],{"class":4109},[1086,18014,11170],{"class":1146},[1086,18016,18017,18019,18022,18024,18026,18028,18030,18032,18034,18036,18038],{"class":1088,"line":729},[1086,18018,11180],{"class":1146},[1086,18020,18021],{"class":4109},"ReleaseId",[1086,18023,17936],{"class":1146},[1086,18025,11193],{"class":4109},[1086,18027,2694],{"class":1146},[1086,18029,11198],{"class":1436},[1086,18031,11201],{"class":1146},[1086,18033,11193],{"class":4109},[1086,18035,17950],{"class":1146},[1086,18037,18021],{"class":4109},[1086,18039,11170],{"class":1146},[1086,18041,18042,18044,18047,18049,18052,18054,18056],{"class":1088,"line":1112},[1086,18043,11180],{"class":1146},[1086,18045,18046],{"class":4109},"ReleaseReference",[1086,18048,2694],{"class":1146},[1086,18050,18051],{"class":1436},"R0",[1086,18053,11201],{"class":1146},[1086,18055,18046],{"class":4109},[1086,18057,11170],{"class":1146},[1086,18059,18060,18062,18065,18067,18069,18071,18073],{"class":1088,"line":1181},[1086,18061,11180],{"class":1146},[1086,18063,18064],{"class":4109},"ReleaseType",[1086,18066,2694],{"class":1146},[1086,18068,8880],{"class":1436},[1086,18070,11201],{"class":1146},[1086,18072,18064],{"class":4109},[1086,18074,11170],{"class":1146},[1086,18076,18077,18079,18082],{"class":1088,"line":1205},[1086,18078,11180],{"class":1146},[1086,18080,18081],{"class":4109},"ReleaseDetailsByTerritory",[1086,18083,11170],{"class":1146},[1086,18085,18086,18088,18090,18092,18094,18096,18098],{"class":1088,"line":1276},[1086,18087,11190],{"class":1146},[1086,18089,16322],{"class":4109},[1086,18091,2694],{"class":1146},[1086,18093,10398],{"class":1436},[1086,18095,11201],{"class":1146},[1086,18097,16322],{"class":4109},[1086,18099,11170],{"class":1146},[1086,18101,18102,18104,18107,18109,18111,18113,18115],{"class":1088,"line":1282},[1086,18103,11190],{"class":1146},[1086,18105,18106],{"class":4109},"DisplayArtistName",[1086,18108,2694],{"class":1146},[1086,18110,17353],{"class":1436},[1086,18112,11201],{"class":1146},[1086,18114,18106],{"class":4109},[1086,18116,11170],{"class":1146},[1086,18118,18119,18121,18123,18125,18127,18129,18131,18133],{"class":1088,"line":1288},[1086,18120,11190],{"class":1146},[1086,18122,8859],{"class":4109},[1086,18124,17879],{"class":1155},[1086,18126,1440],{"class":1146},[1086,18128,1159],{"class":1146},[1086,18130,17886],{"class":1096},[1086,18132,1159],{"class":1146},[1086,18134,11170],{"class":1146},[1086,18136,18137,18139,18141,18143,18146,18148,18150],{"class":1088,"line":2685},[1086,18138,17895],{"class":1146},[1086,18140,17898],{"class":4109},[1086,18142,2694],{"class":1146},[1086,18144,18145],{"class":1436},"ONE",[1086,18147,11201],{"class":1146},[1086,18149,17898],{"class":4109},[1086,18151,11170],{"class":1146},[1086,18153,18154,18156,18158],{"class":1088,"line":2700},[1086,18155,17914],{"class":1146},[1086,18157,8859],{"class":4109},[1086,18159,11170],{"class":1146},[1086,18161,18162,18164,18166],{"class":1088,"line":3398},[1086,18163,11260],{"class":1146},[1086,18165,18081],{"class":4109},[1086,18167,11170],{"class":1146},[1086,18169,18170,18172,18174],{"class":1088,"line":1715},[1086,18171,11201],{"class":1146},[1086,18173,11183],{"class":4109},[1086,18175,11170],{"class":1146},[1074,18177,18179],{"id":18178},"deallist","DealList",[842,18181,5119,18182,18185],{},[996,18183,18184],{},"only"," section that grants commercial rights. This is where territory restrictions live:",[1013,18187,18189],{"className":11154,"code":18188,"language":11157,"meta":728,"style":728},"\u003CReleaseDeal>\n  \u003CDealReleaseReference>R0\u003C/DealReleaseReference>\n  \u003CDeal>\n    \u003CDealTerms>\n      \u003CTerritoryCode>Worldwide\u003C/TerritoryCode>\n      \u003CExcludedTerritoryCode>JP\u003C/ExcludedTerritoryCode>\n      \u003CCommercialModelType>SubscriptionModel\u003C/CommercialModelType>\n      \u003CUsage>\n        \u003CUseType>OnDemandStream\u003C/UseType>\n      \u003C/Usage>\n      \u003CValidityPeriod>\n        \u003CStartDate>2026-03-15\u003C/StartDate>\n      \u003C/ValidityPeriod>\n    \u003C/DealTerms>\n  \u003C/Deal>\n\u003C/ReleaseDeal>\n",[895,18190,18191,18200,18217,18225,18234,18250,18267,18284,18293,18310,18319,18328,18345,18353,18361,18369],{"__ignoreMap":728},[1086,18192,18193,18195,18198],{"class":1088,"line":1089},[1086,18194,11164],{"class":1146},[1086,18196,18197],{"class":4109},"ReleaseDeal",[1086,18199,11170],{"class":1146},[1086,18201,18202,18204,18207,18209,18211,18213,18215],{"class":1088,"line":729},[1086,18203,11180],{"class":1146},[1086,18205,18206],{"class":4109},"DealReleaseReference",[1086,18208,2694],{"class":1146},[1086,18210,18051],{"class":1436},[1086,18212,11201],{"class":1146},[1086,18214,18206],{"class":4109},[1086,18216,11170],{"class":1146},[1086,18218,18219,18221,18223],{"class":1088,"line":1112},[1086,18220,11180],{"class":1146},[1086,18222,11361],{"class":4109},[1086,18224,11170],{"class":1146},[1086,18226,18227,18229,18232],{"class":1088,"line":1181},[1086,18228,11190],{"class":1146},[1086,18230,18231],{"class":4109},"DealTerms",[1086,18233,11170],{"class":1146},[1086,18235,18236,18238,18240,18242,18244,18246,18248],{"class":1088,"line":1205},[1086,18237,17895],{"class":1146},[1086,18239,16322],{"class":4109},[1086,18241,2694],{"class":1146},[1086,18243,10398],{"class":1436},[1086,18245,11201],{"class":1146},[1086,18247,16322],{"class":4109},[1086,18249,11170],{"class":1146},[1086,18251,18252,18254,18257,18259,18261,18263,18265],{"class":1088,"line":1276},[1086,18253,17895],{"class":1146},[1086,18255,18256],{"class":4109},"ExcludedTerritoryCode",[1086,18258,2694],{"class":1146},[1086,18260,17505],{"class":1436},[1086,18262,11201],{"class":1146},[1086,18264,18256],{"class":4109},[1086,18266,11170],{"class":1146},[1086,18268,18269,18271,18274,18276,18278,18280,18282],{"class":1088,"line":1282},[1086,18270,17895],{"class":1146},[1086,18272,18273],{"class":4109},"CommercialModelType",[1086,18275,2694],{"class":1146},[1086,18277,11375],{"class":1436},[1086,18279,11201],{"class":1146},[1086,18281,18273],{"class":4109},[1086,18283,11170],{"class":1146},[1086,18285,18286,18288,18291],{"class":1088,"line":1288},[1086,18287,17895],{"class":1146},[1086,18289,18290],{"class":4109},"Usage",[1086,18292,11170],{"class":1146},[1086,18294,18295,18298,18300,18302,18304,18306,18308],{"class":1088,"line":2685},[1086,18296,18297],{"class":1146},"        \u003C",[1086,18299,11388],{"class":4109},[1086,18301,2694],{"class":1146},[1086,18303,11393],{"class":1436},[1086,18305,11201],{"class":1146},[1086,18307,11388],{"class":4109},[1086,18309,11170],{"class":1146},[1086,18311,18312,18315,18317],{"class":1088,"line":2700},[1086,18313,18314],{"class":1146},"      \u003C/",[1086,18316,18290],{"class":4109},[1086,18318,11170],{"class":1146},[1086,18320,18321,18323,18326],{"class":1088,"line":3398},[1086,18322,17895],{"class":1146},[1086,18324,18325],{"class":4109},"ValidityPeriod",[1086,18327,11170],{"class":1146},[1086,18329,18330,18332,18334,18336,18339,18341,18343],{"class":1088,"line":1715},[1086,18331,18297],{"class":1146},[1086,18333,11406],{"class":4109},[1086,18335,2694],{"class":1146},[1086,18337,18338],{"class":1436},"2026-03-15",[1086,18340,11201],{"class":1146},[1086,18342,11406],{"class":4109},[1086,18344,11170],{"class":1146},[1086,18346,18347,18349,18351],{"class":1088,"line":3409},[1086,18348,18314],{"class":1146},[1086,18350,18325],{"class":4109},[1086,18352,11170],{"class":1146},[1086,18354,18355,18357,18359],{"class":1088,"line":3415},[1086,18356,17914],{"class":1146},[1086,18358,18231],{"class":4109},[1086,18360,11170],{"class":1146},[1086,18362,18363,18365,18367],{"class":1088,"line":3421},[1086,18364,11260],{"class":1146},[1086,18366,11361],{"class":4109},[1086,18368,11170],{"class":1146},[1086,18370,18371,18373,18375],{"class":1088,"line":3427},[1086,18372,11201],{"class":1146},[1086,18374,18197],{"class":4109},[1086,18376,11170],{"class":1146},[1901,18378,18379],{},[842,18380,18381,7826,18384,18387,18388,18391,18392,18394,18395,18398],{},[996,18382,18383],{},"Common DDEX mistake:",[895,18385,18386],{},"DetailsByTerritory"," in ResourceList and ReleaseList describes ",[964,18389,18390],{},"how content is presented"," in different markets. The ",[895,18393,18179],{}," defines ",[964,18396,18397],{},"where content is available",". Confusing these two - applying territory restrictions in metadata instead of deals - is one of the most frequent implementation errors.",[1074,18400,18402],{"id":18401},"territory-restrictions-in-practice","Territory Restrictions in Practice",[842,18404,18405,18406,1133],{},"When a distributor has agreements with partners who hold rights in specific regions, individual tracks must be excluded from those territories. This requires consistent territory entries across ",[996,18407,18408],{},"three XML sections",[991,18410,18411,18416,18421],{},[961,18412,18413,18415],{},[895,18414,17852],{}," in ResourceList",[961,18417,18418,18420],{},[895,18419,18081],{}," in ReleaseList",[961,18422,18423,18425],{},[895,18424,18231],{}," in DealList",[842,18427,18428,18429,18432],{},"Platforms like Revelator handle this at the release level through their UI, but ",[996,18430,18431],{},"track-level territory restrictions within an album"," are where the friction begins. This is exactly the kind of limitation that pushes distributors toward a custom frontend - you model the territory logic in your own system and push the correct per-track restrictions through the API.",[4937,18434],{},[863,18436,18438],{"id":18437},"real-world-gotchas","Real-World Gotchas",[842,18440,18441],{},"Having worked with distributors who use Revelator as their delivery backbone, here are the practical challenges we've encountered:",[1901,18443,18444],{},[842,18445,18446,18449],{},[996,18447,18448],{},"1. Mandatory Human Approval Step","\nRevelator requires a parent account to approve distributions before they reach DSPs. There is no fully automated end-to-end delivery without a human clicking \"approve\" in the web UI. For high-volume operations, this creates a bottleneck that API-only workflows cannot bypass.",[1074,18451,18453],{"id":18452},"_2-full-object-re-submission","2. Full Object Re-submission",[842,18455,18456],{},"There is no PATCH support for releases. Editing a release requires re-submitting the full release object. If you omit files, they are auto-deleted. This means your backend must always maintain the complete release state.",[1074,18458,18460],{"id":18459},"_3-breaking-api-changes","3. Breaking API Changes",[842,18462,18463],{},"Revelator's API has had several breaking changes in 2025-2026:",[871,18465,18466,18476],{},[874,18467,18468],{},[877,18469,18470,18473],{},[880,18471,18472],{},"Change",[880,18474,18475],{},"When",[887,18477,18478,18493,18504,18512],{},[877,18479,18480,18490],{},[892,18481,18482,18483,18486,18487],{},"UPC type changed from ",[895,18484,18485],{},"number"," to ",[895,18488,18489],{},"string",[892,18491,18492],{},"Feb 2026",[877,18494,18495,18501],{},[892,18496,18497,18498],{},"API base URL migrated to ",[895,18499,18500],{},"api.revelator.com",[892,18502,18503],{},"Mar 2026",[877,18505,18506,18509],{},[892,18507,18508],{},"Production/engineering credits became mandatory",[892,18510,18511],{},"Jun 2025",[877,18513,18514,18517],{},[892,18515,18516],{},"Zero-sentinel IDs deprecated",[892,18518,18519],{},"Nov 2025",[842,18521,18522],{},"Your integration layer needs resilience against schema drift.",[1074,18524,18526],{"id":18525},"_4-dsp-specific-ddex-interpretation","4. DSP-Specific DDEX Interpretation",[842,18528,18529],{},"Even when Revelator generates valid ERN XML, each DSP interprets certain elements differently. Spotify's ingestion engine, Apple's Content Provider system, and Amazon's pipeline each have proprietary extensions and quirks. When delivery fails, debugging requires understanding both the DDEX standard and the specific DSP's interpretation.",[1074,18531,18533],{"id":18532},"_5-locked-distribution-states","5. Locked Distribution States",[842,18535,18536],{},"Tracks in certain distribution statuses (-10, -11, -20, -21) become read-only. If you need to modify metadata on a locked track, you may need to initiate a takedown and redeliver, adding complexity to your workflow engine.",[4937,18538],{},[863,18540,18542],{"id":18541},"when-does-option-a-become-a-dead-end","When Does Option A Become a Dead End?",[842,18544,18545],{},"The hybrid approach works well when:",[958,18547,18548,18554,18560,18563],{},[961,18549,18550,18551],{},"Your catalog is under ",[996,18552,18553],{},"50,000 releases",[961,18555,18556,18557],{},"You have fewer than ",[996,18558,18559],{},"30 DSP targets",[961,18561,18562],{},"Territory complexity is moderate (album-level restrictions, not per-track-per-DSP)",[961,18564,18565],{},"Royalty reporting needs are standard (DSP-level aggregates, not sub-publishing splits)",[842,18567,18568],{},"It starts breaking down when:",[871,18570,18571,18581],{},[874,18572,18573],{},[877,18574,18575,18578],{},[880,18576,18577],{},"Signal",[880,18579,18580],{},"Why It Matters",[887,18582,18583,18593,18603,18613],{},[877,18584,18585,18590],{},[892,18586,18587],{},[996,18588,18589],{},"Royalty logic becomes the product",[892,18591,18592],{},"If your competitive advantage is in how you calculate, split, and report royalties - with advances, recoupables, and multi-party splits - you'll outgrow Revelator's royalty engine before its delivery",[877,18594,18595,18600],{},[892,18596,18597],{},[996,18598,18599],{},"You need real-time delivery control",[892,18601,18602],{},"The mandatory approval step and lack of granular delivery status callbacks limit automation at scale",[877,18604,18605,18610],{},[892,18606,18607],{},[996,18608,18609],{},"DSP-specific customization matters",[892,18611,18612],{},"If you need different DDEX profiles per DSP (ERN 3.8.2 for legacy, ERN 4.3 for Spotify), you'll need your own generation pipeline",[877,18614,18615,18620],{},[892,18616,18617],{},[996,18618,18619],{},"Vendor risk becomes unacceptable",[892,18621,18622],{},"Revelator is VC-backed. API changes, pricing shifts, or an acquisition could force migration under pressure",[1572,18624,18625],{},[842,18626,18627,18628,18631],{},"The practical tipping point is usually ",[996,18629,18630],{},"2-3 years"," into the hybrid approach, when the workarounds and API limitations start costing more in engineering time than building the replaced components would.",[4937,18633],{},[863,18635,18637],{"id":18636},"the-hybrid-to-independent-migration-path","The Hybrid-to-Independent Migration Path",[842,18639,18640],{},"The smartest architecture for Option A anticipates Option B:",[991,18642,18643,18649,18655,18661],{},[961,18644,18645,18648],{},[996,18646,18647],{},"Own your metadata."," Never treat Revelator as the source of truth. Your database is canonical; Revelator is a sync target.",[961,18650,18651,18654],{},[996,18652,18653],{},"Abstract the delivery layer."," Build an internal delivery interface that Revelator implements today but could be swapped for direct DSP integrations tomorrow.",[961,18656,18657,18660],{},[996,18658,18659],{},"Build your own rights engine from day one."," Territory restrictions, split sheets, and rights ownership are your core IP. Never delegate this logic to the platform.",[961,18662,18663,18666],{},[996,18664,18665],{},"Invest in DDEX competency."," Understanding ERN generation and validation - even if Revelator handles it today - is essential knowledge for the eventual transition.",[1032,18668,18669],{},[842,18670,18671,18672,18675],{},"This way, when Option A hits its ceiling, you migrate ",[996,18673,18674],{},"component by component"," rather than executing a risky big-bang rewrite.",[4937,18677],{},[863,18679,18681],{"id":18680},"conclusion","Conclusion",[842,18683,18684],{},"For distributors with direct DSP contracts who are hitting the limits of their current platform, the hybrid approach - custom frontend on Revelator's API - is the fastest path to operational control without the multi-year investment of going fully independent.",[842,18686,18687],{},"The key is to build it with migration in mind. Own your metadata, abstract your delivery layer, and invest in DDEX expertise. When the time comes to replace the backend, you'll be swapping a component rather than rebuilding a platform.",[4937,18689],{},[863,18691,18693],{"id":18692},"resources","Resources",[958,18695,18696,18703,18710,18717,18721,18726],{},[961,18697,18698],{},[846,18699,18702],{"href":18700,"rel":18701},"https://api-docs.revelator.com",[850],"Revelator API Documentation",[961,18704,18705],{},[846,18706,18709],{"href":18707,"rel":18708},"https://kb.ddex.net/implementing-each-standard/electronic-release-notification-message-suite-(ern)/",[850],"DDEX ERN Knowledge Base",[961,18711,18712],{},[846,18713,18716],{"href":18714,"rel":18715},"https://ddexvalidator.musictechlab.io/",[850],"MTL DDEX Validator",[961,18718,18719],{},[846,18720,132],{"href":133},[961,18722,18723],{},[846,18724,18725],{"href":157},"Introduction to Generating DDEX Files Using Python",[961,18727,18728,18731],{},[846,18729,18730],{"href":85},"AI-Powered Analytics Dashboard"," - once distribution data flows into your analytics pipeline, make it queryable with natural language",[1680,18733,18734],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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 .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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":728,"searchDepth":729,"depth":729,"links":18736},[18737,18738,18739,18746,18751,18757,18763,18764,18765,18766],{"id":16589,"depth":729,"text":16590},{"id":16655,"depth":729,"text":16656},{"id":16687,"depth":729,"text":16688,"children":18740},[18741,18742,18743,18744,18745],{"id":16697,"depth":1112,"text":16698},{"id":16718,"depth":1112,"text":1745},{"id":16750,"depth":1112,"text":16751},{"id":3191,"depth":1112,"text":16765},{"id":16779,"depth":1112,"text":16780},{"id":16805,"depth":729,"text":16806,"children":18747},[18748,18749,18750],{"id":16929,"depth":1112,"text":16930},{"id":16997,"depth":1112,"text":16998},{"id":17053,"depth":1112,"text":17054},{"id":17769,"depth":729,"text":17770,"children":18752},[18753,18754,18755,18756],{"id":17779,"depth":1112,"text":17780},{"id":17997,"depth":1112,"text":17998},{"id":18178,"depth":1112,"text":18179},{"id":18401,"depth":1112,"text":18402},{"id":18437,"depth":729,"text":18438,"children":18758},[18759,18760,18761,18762],{"id":18452,"depth":1112,"text":18453},{"id":18459,"depth":1112,"text":18460},{"id":18525,"depth":1112,"text":18526},{"id":18532,"depth":1112,"text":18533},{"id":18541,"depth":729,"text":18542},{"id":18636,"depth":729,"text":18637},{"id":18680,"depth":729,"text":18681},{"id":18692,"depth":729,"text":18693},"2026-02-17T00:00:00.000Z","A practical guide for music distributors evaluating a hybrid approach: custom frontend with Revelator's API as the delivery backbone. Covers architecture, DDEX integration, territory handling, and when to go fully independent.",{"src":18770},"/images/blog/musictechlab_blog_building-a-custom-music-delivery-platform-on-the-revelator-api.webp",{"enabled":738,"items":18772},[18773,18775,18777,18779],{"text":18774,"icon":5365},"Revelator claims 100+ DSP integrations but requires a manual approval step before delivery.",{"text":18776,"icon":3847},"Territory restrictions must be consistent across three XML sections or deliveries fail silently.",{"text":18778,"icon":2895},"Own your metadata from day one and treat Revelator as a sync target, not the source of truth.",{"text":18780,"icon":3920},"The hybrid approach typically hits its ceiling after 2-3 years of scaling.",{},{"title":112,"description":18768},[731,18784,11843,3192],"development","j-0HOY7SF2pZKgaTUKG4xI6pYGGOIVWAd9EiQc5LjGU",{"id":18787,"title":542,"authors":18788,"badge":18791,"body":18794,"category":756,"client":723,"date":25855,"description":25856,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":25857,"keyTakeaways":25860,"meta":25870,"navigation":738,"path":543,"seo":25871,"status":723,"stem":544,"tags":25872,"teaser":723,"__hash__":25873},"posts/blog/software-development/integrating-signnow-e-signatures-into-your-django-application.md",[18789],{"name":834,"to":720,"avatar":18790},{"src":722},{"label":18792,"color":18793},"API Integration","#6366f1",{"type":725,"value":18795,"toc":25831},[18796,18799,18812,18815,18819,18822,18894,18897,18901,18904,18933,18936,18973,18977,19003,19009,19077,19090,19093,19297,19301,19307,19311,19314,20238,20260,20264,20267,21001,21005,21008,21022,21025,21422,21426,21429,21834,21838,21848,22589,22592,22598,22602,22605,23891,23911,23915,23918,24605,24610,24613,24712,24715,24768,24772,24775,25156,25160,25163,25633,25637,25640,25644,25654,25658,25661,25665,25675,25679,25682,25686,25697,25700,25703,25821,25828],[842,18797,18798],{},"Electronic signatures have gone from \"nice to have\" to a hard requirement for any platform that deals with contracts, agreements, or compliance documents. Whether you're onboarding beta testers, sending NDAs, or processing deposit confirmations, manually emailing PDFs and chasing wet signatures simply doesn't scale.",[842,18800,18801,18802,18805,18806,18811],{},"At ",[846,18803,12265],{"href":12263,"rel":18804},[850],", we needed a way to automatically send documents for signature as part of our tester onboarding flow - without leaving the Django admin. We chose ",[846,18807,18810],{"href":18808,"rel":18809},"https://www.signnow.com/developers",[850],"airSlate SignNow"," for its developer-friendly REST API, generous sandbox environment, and competitive pricing.",[842,18813,18814],{},"This article walks through how we built the integration end-to-end: authenticating with OAuth2, uploading documents, sending signing invites, handling webhooks, and managing the full document lifecycle - all from within a Django + Celery stack.",[863,18816,18818],{"id":18817},"why-signnow","Why SignNow?",[842,18820,18821],{},"Before diving into code, here's why we picked SignNow over alternatives like DocuSign or HelloSign:",[871,18823,18824,18833],{},[874,18825,18826],{},[877,18827,18828,18831],{},[880,18829,18830],{},"Criteria",[880,18832,11836],{},[887,18834,18835,18844,18854,18864,18874,18884],{},[877,18836,18837,18841],{},[892,18838,18839],{},[996,18840,12303],{},[892,18842,18843],{},"Free, 2,000 signature invites for testing",[877,18845,18846,18851],{},[892,18847,18848],{},[996,18849,18850],{},"API style",[892,18852,18853],{},"Clean REST API with JSON payloads",[877,18855,18856,18861],{},[892,18857,18858],{},[996,18859,18860],{},"Authentication",[892,18862,18863],{},"Standard OAuth2 (password grant)",[877,18865,18866,18871],{},[892,18867,18868],{},[996,18869,18870],{},"Webhooks",[892,18872,18873],{},"Per-document event subscriptions",[877,18875,18876,18881],{},[892,18877,18878],{},[996,18879,18880],{},"Pricing",[892,18882,18883],{},"Significantly cheaper than DocuSign at scale",[877,18885,18886,18891],{},[892,18887,18888],{},[996,18889,18890],{},"SDKs",[892,18892,18893],{},"Official SDKs for Python, Node.js, PHP, Java, C#",[842,18895,18896],{},"For our use case - programmatically sending documents for a single signer - SignNow's API was straightforward and well-documented.",[863,18898,18900],{"id":18899},"architecture-overview","Architecture overview",[842,18902,18903],{},"Here's how the integration fits into our Django application:",[1013,18905,18907],{"className":3341,"code":18906,"language":3343,"meta":728,"style":728},"flowchart TD\n    A[\"Django Admin (trigger sign)\"] --> B[\"Celery Worker (async pipeline)\"]\n    B --> C[\"SignNow API (upload, sign)\"]\n    C -- \"webhook\" --> D[\"Webhook View (POST receiver)\"]\n    D --> E[\"Celery Worker (download PDF)\"]\n",[895,18908,18909,18913,18918,18923,18928],{"__ignoreMap":728},[1086,18910,18911],{"class":1088,"line":1089},[1086,18912,3350],{"class":1436},[1086,18914,18915],{"class":1088,"line":729},[1086,18916,18917],{"class":1436},"    A[\"Django Admin (trigger sign)\"] --> B[\"Celery Worker (async pipeline)\"]\n",[1086,18919,18920],{"class":1088,"line":1112},[1086,18921,18922],{"class":1436},"    B --> C[\"SignNow API (upload, sign)\"]\n",[1086,18924,18925],{"class":1088,"line":1181},[1086,18926,18927],{"class":1436},"    C -- \"webhook\" --> D[\"Webhook View (POST receiver)\"]\n",[1086,18929,18930],{"class":1088,"line":1205},[1086,18931,18932],{"class":1436},"    D --> E[\"Celery Worker (download PDF)\"]\n",[842,18934,18935],{},"The key design decisions:",[991,18937,18938,18944,18957,18963],{},[961,18939,18940,18943],{},[996,18941,18942],{},"Async everything"," - All SignNow API calls happen in Celery tasks, never in the request cycle",[961,18945,18946,18949,18950,18953,18954],{},[996,18947,18948],{},"Generic relations"," - The signing tracker (",[895,18951,18952],{},"SignableDocument",") can attach to any Django model via ",[895,18955,18956],{},"ContentType",[961,18958,18959,18962],{},[996,18960,18961],{},"Idempotent operations"," - The pipeline gracefully handles retries and duplicate webhook events",[961,18964,18965,18968,18969,18972],{},[996,18966,18967],{},"Service layer pattern"," - A thin ",[895,18970,18971],{},"SignNowService"," class wraps all raw API calls",[863,18974,18976],{"id":18975},"step-1-get-your-signnow-api-credentials","Step 1: Get your SignNow API credentials",[991,18978,18979,18986,18995],{},[961,18980,18981,18982],{},"Create a free sandbox account at ",[846,18983,18985],{"href":18808,"rel":18984},[850],"signnow.com/developers",[961,18987,18988,18989,1589,18992],{},"In the API dashboard, create a new application to get your ",[996,18990,18991],{},"Client ID",[996,18993,18994],{},"Client Secret",[961,18996,18997,18998,19000,19001,1410],{},"Note your sandbox base URL: ",[895,18999,12216],{}," (production uses ",[895,19002,12134],{},[842,19004,19005,19006,19008],{},"Add these to your ",[895,19007,1622],{}," file:",[1013,19010,19012],{"className":1080,"code":19011,"language":1082,"meta":728,"style":728},"# SignNow e-signature (https://www.signnow.com/developers)\nSIGNNOW_API_BASE_URL=https://api-eval.signnow.com  # Use api.signnow.com for production\nSIGNNOW_BASIC_AUTH=  # Base64-encoded client_id:client_secret\nSIGNNOW_USERNAME=    # Your SignNow account email\nSIGNNOW_PASSWORD=    # Your SignNow account password\nSIGNNOW_WEBHOOK_SECRET=  # For verifying webhook payloads\nSIGNNOW_WEBHOOK_CALLBACK_URL=  # Your public webhook endpoint\n",[895,19013,19014,19019,19030,19039,19048,19057,19067],{"__ignoreMap":728},[1086,19015,19016],{"class":1088,"line":1089},[1086,19017,19018],{"class":1427},"# SignNow e-signature (https://www.signnow.com/developers)\n",[1086,19020,19021,19023,19025,19027],{"class":1088,"line":729},[1086,19022,12125],{"class":1436},[1086,19024,1440],{"class":1146},[1086,19026,12216],{"class":1096},[1086,19028,19029],{"class":1427},"  # Use api.signnow.com for production\n",[1086,19031,19032,19034,19036],{"class":1088,"line":1112},[1086,19033,12145],{"class":1436},[1086,19035,1440],{"class":1146},[1086,19037,19038],{"class":1427},"  # Base64-encoded client_id:client_secret\n",[1086,19040,19041,19043,19045],{"class":1088,"line":1181},[1086,19042,12165],{"class":1436},[1086,19044,1440],{"class":1146},[1086,19046,19047],{"class":1427},"    # Your SignNow account email\n",[1086,19049,19050,19052,19054],{"class":1088,"line":1205},[1086,19051,12185],{"class":1436},[1086,19053,1440],{"class":1146},[1086,19055,19056],{"class":1427},"    # Your SignNow account password\n",[1086,19058,19059,19062,19064],{"class":1088,"line":1276},[1086,19060,19061],{"class":1436},"SIGNNOW_WEBHOOK_SECRET",[1086,19063,1440],{"class":1146},[1086,19065,19066],{"class":1427},"  # For verifying webhook payloads\n",[1086,19068,19069,19072,19074],{"class":1088,"line":1282},[1086,19070,19071],{"class":1436},"SIGNNOW_WEBHOOK_CALLBACK_URL",[1086,19073,1440],{"class":1146},[1086,19075,19076],{"class":1427},"  # Your public webhook endpoint\n",[1901,19078,19079],{},[842,19080,5119,19081,19083,19084,19086,19087],{},[895,19082,12145],{}," value must be the Base64 encoding of ",[895,19085,11878],{},". You can generate it with: ",[895,19088,19089],{},"echo -n \"your_client_id:your_client_secret\" | base64",[842,19091,19092],{},"Load these in your Django settings:",[1013,19094,19097],{"className":1368,"code":19095,"filename":19096,"language":1250,"meta":728,"style":728},"# SignNow e-signature\nSIGNNOW_API_BASE_URL = env.str(\"SIGNNOW_API_BASE_URL\", default=\"https://api.signnow.com\")\nSIGNNOW_BASIC_AUTH = env.str(\"SIGNNOW_BASIC_AUTH\", default=\"\")\nSIGNNOW_USERNAME = env.str(\"SIGNNOW_USERNAME\", default=\"\")\nSIGNNOW_PASSWORD = env.str(\"SIGNNOW_PASSWORD\", default=\"\")\nSIGNNOW_WEBHOOK_SECRET = env.str(\"SIGNNOW_WEBHOOK_SECRET\", default=\"\")\nSIGNNOW_WEBHOOK_CALLBACK_URL = env.str(\"SIGNNOW_WEBHOOK_CALLBACK_URL\", default=\"\")\n","settings/base.py",[895,19098,19099,19104,19141,19173,19204,19235,19266],{"__ignoreMap":728},[1086,19100,19101],{"class":1088,"line":1089},[1086,19102,19103],{"class":1427},"# SignNow e-signature\n",[1086,19105,19106,19109,19111,19114,19116,19118,19120,19122,19124,19126,19128,19131,19133,19135,19137,19139],{"class":1088,"line":729},[1086,19107,19108],{"class":1436},"SIGNNOW_API_BASE_URL ",[1086,19110,1440],{"class":1146},[1086,19112,19113],{"class":1436}," env",[1086,19115,861],{"class":1146},[1086,19117,4219],{"class":1105},[1086,19119,1398],{"class":1146},[1086,19121,1159],{"class":1146},[1086,19123,12125],{"class":1096},[1086,19125,1159],{"class":1146},[1086,19127,1227],{"class":1146},[1086,19129,19130],{"class":1401}," default",[1086,19132,1440],{"class":1146},[1086,19134,1159],{"class":1146},[1086,19136,12134],{"class":1096},[1086,19138,1159],{"class":1146},[1086,19140,1455],{"class":1146},[1086,19142,19143,19146,19148,19150,19152,19154,19156,19158,19160,19162,19164,19166,19168,19171],{"class":1088,"line":1112},[1086,19144,19145],{"class":1436},"SIGNNOW_BASIC_AUTH ",[1086,19147,1440],{"class":1146},[1086,19149,19113],{"class":1436},[1086,19151,861],{"class":1146},[1086,19153,4219],{"class":1105},[1086,19155,1398],{"class":1146},[1086,19157,1159],{"class":1146},[1086,19159,12145],{"class":1096},[1086,19161,1159],{"class":1146},[1086,19163,1227],{"class":1146},[1086,19165,19130],{"class":1401},[1086,19167,1440],{"class":1146},[1086,19169,19170],{"class":1146},"\"\"",[1086,19172,1455],{"class":1146},[1086,19174,19175,19178,19180,19182,19184,19186,19188,19190,19192,19194,19196,19198,19200,19202],{"class":1088,"line":1181},[1086,19176,19177],{"class":1436},"SIGNNOW_USERNAME ",[1086,19179,1440],{"class":1146},[1086,19181,19113],{"class":1436},[1086,19183,861],{"class":1146},[1086,19185,4219],{"class":1105},[1086,19187,1398],{"class":1146},[1086,19189,1159],{"class":1146},[1086,19191,12165],{"class":1096},[1086,19193,1159],{"class":1146},[1086,19195,1227],{"class":1146},[1086,19197,19130],{"class":1401},[1086,19199,1440],{"class":1146},[1086,19201,19170],{"class":1146},[1086,19203,1455],{"class":1146},[1086,19205,19206,19209,19211,19213,19215,19217,19219,19221,19223,19225,19227,19229,19231,19233],{"class":1088,"line":1205},[1086,19207,19208],{"class":1436},"SIGNNOW_PASSWORD ",[1086,19210,1440],{"class":1146},[1086,19212,19113],{"class":1436},[1086,19214,861],{"class":1146},[1086,19216,4219],{"class":1105},[1086,19218,1398],{"class":1146},[1086,19220,1159],{"class":1146},[1086,19222,12185],{"class":1096},[1086,19224,1159],{"class":1146},[1086,19226,1227],{"class":1146},[1086,19228,19130],{"class":1401},[1086,19230,1440],{"class":1146},[1086,19232,19170],{"class":1146},[1086,19234,1455],{"class":1146},[1086,19236,19237,19240,19242,19244,19246,19248,19250,19252,19254,19256,19258,19260,19262,19264],{"class":1088,"line":1276},[1086,19238,19239],{"class":1436},"SIGNNOW_WEBHOOK_SECRET ",[1086,19241,1440],{"class":1146},[1086,19243,19113],{"class":1436},[1086,19245,861],{"class":1146},[1086,19247,4219],{"class":1105},[1086,19249,1398],{"class":1146},[1086,19251,1159],{"class":1146},[1086,19253,19061],{"class":1096},[1086,19255,1159],{"class":1146},[1086,19257,1227],{"class":1146},[1086,19259,19130],{"class":1401},[1086,19261,1440],{"class":1146},[1086,19263,19170],{"class":1146},[1086,19265,1455],{"class":1146},[1086,19267,19268,19271,19273,19275,19277,19279,19281,19283,19285,19287,19289,19291,19293,19295],{"class":1088,"line":1282},[1086,19269,19270],{"class":1436},"SIGNNOW_WEBHOOK_CALLBACK_URL ",[1086,19272,1440],{"class":1146},[1086,19274,19113],{"class":1436},[1086,19276,861],{"class":1146},[1086,19278,4219],{"class":1105},[1086,19280,1398],{"class":1146},[1086,19282,1159],{"class":1146},[1086,19284,19071],{"class":1096},[1086,19286,1159],{"class":1146},[1086,19288,1227],{"class":1146},[1086,19290,19130],{"class":1401},[1086,19292,1440],{"class":1146},[1086,19294,19170],{"class":1146},[1086,19296,1455],{"class":1146},[863,19298,19300],{"id":19299},"step-2-build-the-api-client-service-layer","Step 2: Build the API client (service layer)",[842,19302,19303,19304,19306],{},"Rather than scattering HTTP calls throughout the codebase, we encapsulated all SignNow API interactions in a single ",[895,19305,18971],{}," class. This makes testing, error handling, and future changes much simpler.",[1074,19308,19310],{"id":19309},"oauth2-authentication-with-token-caching","OAuth2 authentication with token caching",[842,19312,19313],{},"SignNow uses the OAuth2 password grant to obtain access tokens. Tokens expire after a configurable period (typically 1 hour), so we cache them in Redis with a safety buffer:",[1013,19315,19318],{"className":1368,"code":19316,"filename":19317,"language":1250,"meta":728,"style":728},"import httpx\nfrom django.conf import settings\nfrom django.core.cache import cache\n\nSIGNNOW_TOKEN_CACHE_KEY = \"signnow_access_token\"\nSIGNNOW_TOKEN_TTL_BUFFER = 300  # Refresh 5 min before expiry\n\n\nclass SignNowService:\n    \"\"\"Raw REST client for the SignNow API using httpx.\"\"\"\n\n    _initialized = False\n    _base_url = \"\"\n    _basic_auth = \"\"\n    _username = \"\"\n    _password = \"\"\n\n    @classmethod\n    def _ensure_initialized(cls) -> bool:\n        \"\"\"Initialize SignNow configuration from settings.\"\"\"\n        if cls._initialized:\n            return True\n\n        cls._base_url = getattr(settings, \"SIGNNOW_API_BASE_URL\", \"\")\n        cls._basic_auth = getattr(settings, \"SIGNNOW_BASIC_AUTH\", \"\")\n        cls._username = getattr(settings, \"SIGNNOW_USERNAME\", \"\")\n        cls._password = getattr(settings, \"SIGNNOW_PASSWORD\", \"\")\n\n        if not all([cls._base_url, cls._basic_auth, cls._username, cls._password]):\n            logger.warning(\"SignNow API not fully configured - missing required settings\")\n            return False\n\n        cls._initialized = True\n        return True\n\n    @classmethod\n    def _get_access_token(cls) -> str | None:\n        \"\"\"Get a valid access token, using cache or requesting a new one.\"\"\"\n        cached = cache.get(SIGNNOW_TOKEN_CACHE_KEY)\n        if cached:\n            return cached\n\n        with httpx.Client(timeout=30.0) as client:\n            resp = client.post(\n                f\"{cls._base_url}/oauth2/token\",\n                data={\n                    \"grant_type\": \"password\",\n                    \"username\": cls._username,\n                    \"password\": cls._password,\n                    \"scope\": \"*\",\n                },\n                headers={\n                    \"Authorization\": f\"Basic {cls._basic_auth}\",\n                    \"Content-Type\": \"application/x-www-form-urlencoded\",\n                },\n            )\n            resp.raise_for_status()\n            data = resp.json()\n\n            token = data[\"access_token\"]\n            expires_in = data.get(\"expires_in\", 3600)\n            ttl = max(expires_in - SIGNNOW_TOKEN_TTL_BUFFER, 60)\n            cache.set(SIGNNOW_TOKEN_CACHE_KEY, token, timeout=ttl)\n\n            return token\n","apps/signnow/services/signnow_service.py",[895,19319,19320,19326,19343,19364,19368,19382,19395,19399,19403,19412,19421,19425,19435,19445,19454,19463,19472,19476,19483,19504,19514,19528,19536,19540,19574,19605,19636,19667,19671,19715,19735,19741,19745,19757,19763,19767,19773,19795,19804,19825,19834,19841,19846,19878,19894,19917,19925,19946,19966,19985,20005,20011,20019,20049,20070,20075,20081,20093,20110,20115,20137,20167,20195,20225,20230],{"__ignoreMap":728},[1086,19321,19322,19324],{"class":1088,"line":1089},[1086,19323,6503],{"class":1423},[1086,19325,17069],{"class":1436},[1086,19327,19328,19330,19333,19335,19338,19340],{"class":1088,"line":729},[1086,19329,15570],{"class":1423},[1086,19331,19332],{"class":1436}," django",[1086,19334,861],{"class":1146},[1086,19336,19337],{"class":1436},"conf ",[1086,19339,6503],{"class":1423},[1086,19341,19342],{"class":1436}," settings\n",[1086,19344,19345,19347,19349,19351,19354,19356,19359,19361],{"class":1088,"line":1112},[1086,19346,15570],{"class":1423},[1086,19348,19332],{"class":1436},[1086,19350,861],{"class":1146},[1086,19352,19353],{"class":1436},"core",[1086,19355,861],{"class":1146},[1086,19357,19358],{"class":1436},"cache ",[1086,19360,6503],{"class":1423},[1086,19362,19363],{"class":1436}," cache\n",[1086,19365,19366],{"class":1088,"line":1181},[1086,19367,3390],{"emptyLinePlaceholder":738},[1086,19369,19370,19373,19375,19377,19380],{"class":1088,"line":1205},[1086,19371,19372],{"class":1436},"SIGNNOW_TOKEN_CACHE_KEY ",[1086,19374,1440],{"class":1146},[1086,19376,1195],{"class":1146},[1086,19378,19379],{"class":1096},"signnow_access_token",[1086,19381,4441],{"class":1146},[1086,19383,19384,19387,19389,19392],{"class":1088,"line":1276},[1086,19385,19386],{"class":1436},"SIGNNOW_TOKEN_TTL_BUFFER ",[1086,19388,1440],{"class":1146},[1086,19390,19391],{"class":1187}," 300",[1086,19393,19394],{"class":1427},"  # Refresh 5 min before expiry\n",[1086,19396,19397],{"class":1088,"line":1282},[1086,19398,3390],{"emptyLinePlaceholder":738},[1086,19400,19401],{"class":1088,"line":1288},[1086,19402,3390],{"emptyLinePlaceholder":738},[1086,19404,19405,19407,19410],{"class":1088,"line":2685},[1086,19406,4036],{"class":1155},[1086,19408,19409],{"class":1092}," SignNowService",[1086,19411,1418],{"class":1146},[1086,19413,19414,19416,19419],{"class":1088,"line":2700},[1086,19415,1424],{"class":1423},[1086,19417,19418],{"class":1427},"Raw REST client for the SignNow API using httpx.",[1086,19420,1431],{"class":1423},[1086,19422,19423],{"class":1088,"line":3398},[1086,19424,3390],{"emptyLinePlaceholder":738},[1086,19426,19427,19430,19432],{"class":1088,"line":1715},[1086,19428,19429],{"class":1436},"    _initialized ",[1086,19431,1440],{"class":1146},[1086,19433,19434],{"class":1146}," False\n",[1086,19436,19437,19440,19442],{"class":1088,"line":3409},[1086,19438,19439],{"class":1436},"    _base_url ",[1086,19441,1440],{"class":1146},[1086,19443,19444],{"class":1146}," \"\"\n",[1086,19446,19447,19450,19452],{"class":1088,"line":3415},[1086,19448,19449],{"class":1436},"    _basic_auth ",[1086,19451,1440],{"class":1146},[1086,19453,19444],{"class":1146},[1086,19455,19456,19459,19461],{"class":1088,"line":3421},[1086,19457,19458],{"class":1436},"    _username ",[1086,19460,1440],{"class":1146},[1086,19462,19444],{"class":1146},[1086,19464,19465,19468,19470],{"class":1088,"line":3427},[1086,19466,19467],{"class":1436},"    _password ",[1086,19469,1440],{"class":1146},[1086,19471,19444],{"class":1146},[1086,19473,19474],{"class":1088,"line":3433},[1086,19475,3390],{"emptyLinePlaceholder":738},[1086,19477,19478,19480],{"class":1088,"line":3439},[1086,19479,6734],{"class":1146},[1086,19481,19482],{"class":1092},"classmethod\n",[1086,19484,19485,19487,19490,19492,19495,19497,19499,19502],{"class":1088,"line":3444},[1086,19486,4065],{"class":1155},[1086,19488,19489],{"class":1105}," _ensure_initialized",[1086,19491,1398],{"class":1146},[1086,19493,19494],{"class":1401},"cls",[1086,19496,1410],{"class":1146},[1086,19498,1413],{"class":1146},[1086,19500,19501],{"class":1092}," bool",[1086,19503,1418],{"class":1146},[1086,19505,19506,19509,19512],{"class":1088,"line":3450},[1086,19507,19508],{"class":1423},"        \"\"\"",[1086,19510,19511],{"class":1427},"Initialize SignNow configuration from settings.",[1086,19513,1431],{"class":1423},[1086,19515,19516,19518,19521,19523,19526],{"class":1088,"line":3456},[1086,19517,6800],{"class":1423},[1086,19519,19520],{"class":1436}," cls",[1086,19522,861],{"class":1146},[1086,19524,19525],{"class":4109},"_initialized",[1086,19527,1418],{"class":1146},[1086,19529,19530,19533],{"class":1088,"line":3462},[1086,19531,19532],{"class":1423},"            return",[1086,19534,19535],{"class":1146}," True\n",[1086,19537,19538],{"class":1088,"line":3467},[1086,19539,3390],{"emptyLinePlaceholder":738},[1086,19541,19542,19545,19547,19550,19553,19555,19557,19559,19561,19563,19565,19567,19569,19572],{"class":1088,"line":3473},[1086,19543,19544],{"class":1436},"        cls",[1086,19546,861],{"class":1146},[1086,19548,19549],{"class":4109},"_base_url",[1086,19551,19552],{"class":1146}," =",[1086,19554,6441],{"class":1105},[1086,19556,1398],{"class":1146},[1086,19558,4104],{"class":1105},[1086,19560,1227],{"class":1146},[1086,19562,1195],{"class":1146},[1086,19564,12125],{"class":1096},[1086,19566,1159],{"class":1146},[1086,19568,1227],{"class":1146},[1086,19570,19571],{"class":1146}," \"\"",[1086,19573,1455],{"class":1146},[1086,19575,19576,19578,19580,19583,19585,19587,19589,19591,19593,19595,19597,19599,19601,19603],{"class":1088,"line":3479},[1086,19577,19544],{"class":1436},[1086,19579,861],{"class":1146},[1086,19581,19582],{"class":4109},"_basic_auth",[1086,19584,19552],{"class":1146},[1086,19586,6441],{"class":1105},[1086,19588,1398],{"class":1146},[1086,19590,4104],{"class":1105},[1086,19592,1227],{"class":1146},[1086,19594,1195],{"class":1146},[1086,19596,12145],{"class":1096},[1086,19598,1159],{"class":1146},[1086,19600,1227],{"class":1146},[1086,19602,19571],{"class":1146},[1086,19604,1455],{"class":1146},[1086,19606,19607,19609,19611,19614,19616,19618,19620,19622,19624,19626,19628,19630,19632,19634],{"class":1088,"line":3485},[1086,19608,19544],{"class":1436},[1086,19610,861],{"class":1146},[1086,19612,19613],{"class":4109},"_username",[1086,19615,19552],{"class":1146},[1086,19617,6441],{"class":1105},[1086,19619,1398],{"class":1146},[1086,19621,4104],{"class":1105},[1086,19623,1227],{"class":1146},[1086,19625,1195],{"class":1146},[1086,19627,12165],{"class":1096},[1086,19629,1159],{"class":1146},[1086,19631,1227],{"class":1146},[1086,19633,19571],{"class":1146},[1086,19635,1455],{"class":1146},[1086,19637,19638,19640,19642,19645,19647,19649,19651,19653,19655,19657,19659,19661,19663,19665],{"class":1088,"line":3491},[1086,19639,19544],{"class":1436},[1086,19641,861],{"class":1146},[1086,19643,19644],{"class":4109},"_password",[1086,19646,19552],{"class":1146},[1086,19648,6441],{"class":1105},[1086,19650,1398],{"class":1146},[1086,19652,4104],{"class":1105},[1086,19654,1227],{"class":1146},[1086,19656,1195],{"class":1146},[1086,19658,12185],{"class":1096},[1086,19660,1159],{"class":1146},[1086,19662,1227],{"class":1146},[1086,19664,19571],{"class":1146},[1086,19666,1455],{"class":1146},[1086,19668,19669],{"class":1088,"line":3497},[1086,19670,3390],{"emptyLinePlaceholder":738},[1086,19672,19673,19675,19677,19680,19682,19684,19686,19688,19690,19692,19694,19696,19698,19700,19702,19704,19706,19708,19710,19712],{"class":1088,"line":3503},[1086,19674,6800],{"class":1423},[1086,19676,6803],{"class":1146},[1086,19678,19679],{"class":1105}," all",[1086,19681,4367],{"class":1146},[1086,19683,19494],{"class":1436},[1086,19685,861],{"class":1146},[1086,19687,19549],{"class":4109},[1086,19689,1227],{"class":1146},[1086,19691,19520],{"class":1436},[1086,19693,861],{"class":1146},[1086,19695,19582],{"class":4109},[1086,19697,1227],{"class":1146},[1086,19699,19520],{"class":1436},[1086,19701,861],{"class":1146},[1086,19703,19613],{"class":4109},[1086,19705,1227],{"class":1146},[1086,19707,19520],{"class":1436},[1086,19709,861],{"class":1146},[1086,19711,19644],{"class":4109},[1086,19713,19714],{"class":1146},"]):\n",[1086,19716,19717,19720,19722,19724,19726,19728,19731,19733],{"class":1088,"line":3509},[1086,19718,19719],{"class":1436},"            logger",[1086,19721,861],{"class":1146},[1086,19723,1901],{"class":1105},[1086,19725,1398],{"class":1146},[1086,19727,1159],{"class":1146},[1086,19729,19730],{"class":1096},"SignNow API not fully configured - missing required settings",[1086,19732,1159],{"class":1146},[1086,19734,1455],{"class":1146},[1086,19736,19737,19739],{"class":1088,"line":3515},[1086,19738,19532],{"class":1423},[1086,19740,19434],{"class":1146},[1086,19742,19743],{"class":1088,"line":3520},[1086,19744,3390],{"emptyLinePlaceholder":738},[1086,19746,19747,19749,19751,19753,19755],{"class":1088,"line":3526},[1086,19748,19544],{"class":1436},[1086,19750,861],{"class":1146},[1086,19752,19525],{"class":4109},[1086,19754,19552],{"class":1146},[1086,19756,19535],{"class":1146},[1086,19758,19759,19761],{"class":1088,"line":3531},[1086,19760,4239],{"class":1423},[1086,19762,19535],{"class":1146},[1086,19764,19765],{"class":1088,"line":3537},[1086,19766,3390],{"emptyLinePlaceholder":738},[1086,19768,19769,19771],{"class":1088,"line":3543},[1086,19770,6734],{"class":1146},[1086,19772,19482],{"class":1092},[1086,19774,19775,19777,19780,19782,19784,19786,19788,19790,19792],{"class":1088,"line":3549},[1086,19776,4065],{"class":1155},[1086,19778,19779],{"class":1105}," _get_access_token",[1086,19781,1398],{"class":1146},[1086,19783,19494],{"class":1401},[1086,19785,1410],{"class":1146},[1086,19787,1413],{"class":1146},[1086,19789,1407],{"class":1092},[1086,19791,11883],{"class":1146},[1086,19793,19794],{"class":1146}," None:\n",[1086,19796,19797,19799,19802],{"class":1088,"line":3555},[1086,19798,19508],{"class":1423},[1086,19800,19801],{"class":1427},"Get a valid access token, using cache or requesting a new one.",[1086,19803,1431],{"class":1423},[1086,19805,19806,19809,19811,19814,19816,19818,19820,19823],{"class":1088,"line":3561},[1086,19807,19808],{"class":1436},"        cached ",[1086,19810,1440],{"class":1146},[1086,19812,19813],{"class":1436}," cache",[1086,19815,861],{"class":1146},[1086,19817,10812],{"class":1105},[1086,19819,1398],{"class":1146},[1086,19821,19822],{"class":1105},"SIGNNOW_TOKEN_CACHE_KEY",[1086,19824,1455],{"class":1146},[1086,19826,19827,19829,19832],{"class":1088,"line":3567},[1086,19828,6800],{"class":1423},[1086,19830,19831],{"class":1436}," cached",[1086,19833,1418],{"class":1146},[1086,19835,19836,19838],{"class":1088,"line":17749},[1086,19837,19532],{"class":1423},[1086,19839,19840],{"class":1436}," cached\n",[1086,19842,19844],{"class":1088,"line":19843},42,[1086,19845,3390],{"emptyLinePlaceholder":738},[1086,19847,19849,19852,19854,19856,19859,19861,19864,19866,19869,19871,19874,19876],{"class":1088,"line":19848},43,[1086,19850,19851],{"class":1423},"        with",[1086,19853,7160],{"class":1436},[1086,19855,861],{"class":1146},[1086,19857,19858],{"class":1105},"Client",[1086,19860,1398],{"class":1146},[1086,19862,19863],{"class":1401},"timeout",[1086,19865,1440],{"class":1146},[1086,19867,19868],{"class":1187},"30.0",[1086,19870,1410],{"class":1146},[1086,19872,19873],{"class":1423}," as",[1086,19875,1443],{"class":1436},[1086,19877,1418],{"class":1146},[1086,19879,19881,19884,19886,19888,19890,19892],{"class":1088,"line":19880},44,[1086,19882,19883],{"class":1436},"            resp ",[1086,19885,1440],{"class":1146},[1086,19887,1443],{"class":1436},[1086,19889,861],{"class":1146},[1086,19891,7165],{"class":1105},[1086,19893,4094],{"class":1146},[1086,19895,19897,19900,19902,19904,19906,19908,19910,19912,19915],{"class":1088,"line":19896},45,[1086,19898,19899],{"class":1155},"                f",[1086,19901,1159],{"class":1096},[1086,19903,4409],{"class":1187},[1086,19905,19494],{"class":1436},[1086,19907,861],{"class":1146},[1086,19909,19549],{"class":4109},[1086,19911,4423],{"class":1187},[1086,19913,19914],{"class":1096},"/oauth2/token\"",[1086,19916,1202],{"class":1146},[1086,19918,19920,19923],{"class":1088,"line":19919},46,[1086,19921,19922],{"class":1401},"                data",[1086,19924,7203],{"class":1146},[1086,19926,19928,19931,19934,19936,19938,19940,19942,19944],{"class":1088,"line":19927},47,[1086,19929,19930],{"class":1146},"                    \"",[1086,19932,19933],{"class":1096},"grant_type",[1086,19935,1159],{"class":1146},[1086,19937,1133],{"class":1146},[1086,19939,1195],{"class":1146},[1086,19941,17158],{"class":1096},[1086,19943,1159],{"class":1146},[1086,19945,1202],{"class":1146},[1086,19947,19949,19951,19954,19956,19958,19960,19962,19964],{"class":1088,"line":19948},48,[1086,19950,19930],{"class":1146},[1086,19952,19953],{"class":1096},"username",[1086,19955,1159],{"class":1146},[1086,19957,1133],{"class":1146},[1086,19959,19520],{"class":1436},[1086,19961,861],{"class":1146},[1086,19963,19613],{"class":4109},[1086,19965,1202],{"class":1146},[1086,19967,19969,19971,19973,19975,19977,19979,19981,19983],{"class":1088,"line":19968},49,[1086,19970,19930],{"class":1146},[1086,19972,17158],{"class":1096},[1086,19974,1159],{"class":1146},[1086,19976,1133],{"class":1146},[1086,19978,19520],{"class":1436},[1086,19980,861],{"class":1146},[1086,19982,19644],{"class":4109},[1086,19984,1202],{"class":1146},[1086,19986,19988,19990,19993,19995,19997,19999,20001,20003],{"class":1088,"line":19987},50,[1086,19989,19930],{"class":1146},[1086,19991,19992],{"class":1096},"scope",[1086,19994,1159],{"class":1146},[1086,19996,1133],{"class":1146},[1086,19998,1195],{"class":1146},[1086,20000,2775],{"class":1096},[1086,20002,1159],{"class":1146},[1086,20004,1202],{"class":1146},[1086,20006,20008],{"class":1088,"line":20007},51,[1086,20009,20010],{"class":1146},"                },\n",[1086,20012,20014,20017],{"class":1088,"line":20013},52,[1086,20015,20016],{"class":1401},"                headers",[1086,20018,7203],{"class":1146},[1086,20020,20022,20024,20026,20028,20030,20032,20035,20037,20039,20041,20043,20045,20047],{"class":1088,"line":20021},53,[1086,20023,19930],{"class":1146},[1086,20025,17211],{"class":1096},[1086,20027,1159],{"class":1146},[1086,20029,1133],{"class":1146},[1086,20031,4403],{"class":1155},[1086,20033,20034],{"class":1096},"\"Basic ",[1086,20036,4409],{"class":1187},[1086,20038,19494],{"class":1436},[1086,20040,861],{"class":1146},[1086,20042,19582],{"class":4109},[1086,20044,4423],{"class":1187},[1086,20046,1159],{"class":1096},[1086,20048,1202],{"class":1146},[1086,20050,20052,20054,20057,20059,20061,20063,20066,20068],{"class":1088,"line":20051},54,[1086,20053,19930],{"class":1146},[1086,20055,20056],{"class":1096},"Content-Type",[1086,20058,1159],{"class":1146},[1086,20060,1133],{"class":1146},[1086,20062,1195],{"class":1146},[1086,20064,20065],{"class":1096},"application/x-www-form-urlencoded",[1086,20067,1159],{"class":1146},[1086,20069,1202],{"class":1146},[1086,20071,20073],{"class":1088,"line":20072},55,[1086,20074,20010],{"class":1146},[1086,20076,20078],{"class":1088,"line":20077},56,[1086,20079,20080],{"class":1146},"            )\n",[1086,20082,20084,20087,20089,20091],{"class":1088,"line":20083},57,[1086,20085,20086],{"class":1436},"            resp",[1086,20088,861],{"class":1146},[1086,20090,15716],{"class":1105},[1086,20092,1387],{"class":1146},[1086,20094,20096,20099,20101,20104,20106,20108],{"class":1088,"line":20095},58,[1086,20097,20098],{"class":1436},"            data ",[1086,20100,1440],{"class":1146},[1086,20102,20103],{"class":1436}," resp",[1086,20105,861],{"class":1146},[1086,20107,1139],{"class":1105},[1086,20109,1387],{"class":1146},[1086,20111,20113],{"class":1088,"line":20112},59,[1086,20114,3390],{"emptyLinePlaceholder":738},[1086,20116,20118,20121,20123,20126,20128,20130,20133,20135],{"class":1088,"line":20117},60,[1086,20119,20120],{"class":1436},"            token ",[1086,20122,1440],{"class":1146},[1086,20124,20125],{"class":1436}," data",[1086,20127,4340],{"class":1146},[1086,20129,1159],{"class":1146},[1086,20131,20132],{"class":1096},"access_token",[1086,20134,1159],{"class":1146},[1086,20136,1273],{"class":1146},[1086,20138,20140,20143,20145,20147,20149,20151,20153,20155,20158,20160,20162,20165],{"class":1088,"line":20139},61,[1086,20141,20142],{"class":1436},"            expires_in ",[1086,20144,1440],{"class":1146},[1086,20146,20125],{"class":1436},[1086,20148,861],{"class":1146},[1086,20150,10812],{"class":1105},[1086,20152,1398],{"class":1146},[1086,20154,1159],{"class":1146},[1086,20156,20157],{"class":1096},"expires_in",[1086,20159,1159],{"class":1146},[1086,20161,1227],{"class":1146},[1086,20163,20164],{"class":1187}," 3600",[1086,20166,1455],{"class":1146},[1086,20168,20170,20173,20175,20178,20180,20183,20185,20188,20190,20193],{"class":1088,"line":20169},62,[1086,20171,20172],{"class":1436},"            ttl ",[1086,20174,1440],{"class":1146},[1086,20176,20177],{"class":1105}," max",[1086,20179,1398],{"class":1146},[1086,20181,20182],{"class":1105},"expires_in ",[1086,20184,2635],{"class":1146},[1086,20186,20187],{"class":1105}," SIGNNOW_TOKEN_TTL_BUFFER",[1086,20189,1227],{"class":1146},[1086,20191,20192],{"class":1187}," 60",[1086,20194,1455],{"class":1146},[1086,20196,20198,20201,20203,20206,20208,20210,20212,20214,20216,20218,20220,20223],{"class":1088,"line":20197},63,[1086,20199,20200],{"class":1436},"            cache",[1086,20202,861],{"class":1146},[1086,20204,20205],{"class":1105},"set",[1086,20207,1398],{"class":1146},[1086,20209,19822],{"class":1105},[1086,20211,1227],{"class":1146},[1086,20213,4256],{"class":1105},[1086,20215,1227],{"class":1146},[1086,20217,15700],{"class":1401},[1086,20219,1440],{"class":1146},[1086,20221,20222],{"class":1105},"ttl",[1086,20224,1455],{"class":1146},[1086,20226,20228],{"class":1088,"line":20227},64,[1086,20229,3390],{"emptyLinePlaceholder":738},[1086,20231,20233,20235],{"class":1088,"line":20232},65,[1086,20234,19532],{"class":1423},[1086,20236,20237],{"class":1436}," token\n",[1032,20239,20240],{},[842,20241,20242,20243,20248,20249,20252,20253,20255,20256,20259],{},"We use ",[846,20244,20247],{"href":20245,"rel":20246},"https://www.python-httpx.org/",[850],"httpx"," instead of ",[895,20250,20251],{},"requests"," for its modern API, built-in timeout support, and async capabilities. If you later need to make concurrent API calls, ",[895,20254,20247],{}," supports ",[895,20257,20258],{},"AsyncClient"," out of the box.",[1074,20261,20263],{"id":20262},"document-operations","Document operations",[842,20265,20266],{},"With authentication handled, the core API methods are straightforward:",[1013,20268,20270],{"className":1368,"code":20269,"filename":19317,"language":1250,"meta":728,"style":728},"@classmethod\ndef upload_document(cls, pdf_bytes: bytes, filename: str) -> dict | None:\n    \"\"\"Upload a PDF to SignNow. Returns {\"id\": \"document_id\"} on success.\"\"\"\n    token = cls._get_access_token()\n    if not token:\n        return None\n\n    with httpx.Client(timeout=60.0) as client:\n        resp = client.post(\n            f\"{cls._base_url}/document\",\n            headers=cls._auth_headers(token),\n            files={\"file\": (filename, pdf_bytes, \"application/pdf\")},\n        )\n        resp.raise_for_status()\n        return {\"id\": resp.json().get(\"id\", \"\")}\n\n@classmethod\ndef add_signature_fields(cls, document_id: str, fields: list[dict]) -> bool:\n    \"\"\"Add signature/text fields to an uploaded document.\"\"\"\n    token = cls._get_access_token()\n    if not token:\n        return False\n\n    with httpx.Client(timeout=30.0) as client:\n        resp = client.put(\n            f\"{cls._base_url}/document/{document_id}\",\n            headers={**cls._auth_headers(token), \"Content-Type\": \"application/json\"},\n            json={\"fields\": fields},\n        )\n        resp.raise_for_status()\n        return True\n\n@classmethod\ndef download_signed_document(cls, document_id: str) -> bytes | None:\n    \"\"\"Download the signed (collapsed) PDF.\"\"\"\n    token = cls._get_access_token()\n    if not token:\n        return None\n\n    with httpx.Client(timeout=60.0) as client:\n        resp = client.get(\n            f\"{cls._base_url}/document/{document_id}/download\",\n            headers=cls._auth_headers(token),\n            params={\"type\": \"collapsed\"},\n        )\n        resp.raise_for_status()\n        return resp.content\n",[895,20271,20272,20278,20318,20327,20343,20353,20360,20364,20392,20407,20429,20450,20486,20490,20501,20540,20544,20550,20594,20603,20617,20627,20633,20637,20663,20678,20708,20744,20763,20767,20777,20783,20787,20793,20822,20831,20845,20855,20861,20865,20891,20905,20934,20952,20976,20980,20990],{"__ignoreMap":728},[1086,20273,20274,20276],{"class":1088,"line":1089},[1086,20275,1376],{"class":1146},[1086,20277,19482],{"class":1092},[1086,20279,20280,20282,20285,20287,20289,20291,20294,20296,20299,20301,20304,20306,20308,20310,20312,20314,20316],{"class":1088,"line":729},[1086,20281,1392],{"class":1155},[1086,20283,20284],{"class":1105}," upload_document",[1086,20286,1398],{"class":1146},[1086,20288,19494],{"class":1401},[1086,20290,1227],{"class":1146},[1086,20292,20293],{"class":1401}," pdf_bytes",[1086,20295,1133],{"class":1146},[1086,20297,20298],{"class":1092}," bytes",[1086,20300,1227],{"class":1146},[1086,20302,20303],{"class":1401}," filename",[1086,20305,1133],{"class":1146},[1086,20307,1407],{"class":1092},[1086,20309,1410],{"class":1146},[1086,20311,1413],{"class":1146},[1086,20313,1518],{"class":1092},[1086,20315,11883],{"class":1146},[1086,20317,19794],{"class":1146},[1086,20319,20320,20322,20325],{"class":1088,"line":1112},[1086,20321,1424],{"class":1423},[1086,20323,20324],{"class":1427},"Upload a PDF to SignNow. Returns {\"id\": \"document_id\"} on success.",[1086,20326,1431],{"class":1423},[1086,20328,20329,20332,20334,20336,20338,20341],{"class":1088,"line":1181},[1086,20330,20331],{"class":1436},"    token ",[1086,20333,1440],{"class":1146},[1086,20335,19520],{"class":1436},[1086,20337,861],{"class":1146},[1086,20339,20340],{"class":1105},"_get_access_token",[1086,20342,1387],{"class":1146},[1086,20344,20345,20347,20349,20351],{"class":1088,"line":1205},[1086,20346,6474],{"class":1423},[1086,20348,6803],{"class":1146},[1086,20350,4256],{"class":1436},[1086,20352,1418],{"class":1146},[1086,20354,20355,20357],{"class":1088,"line":1276},[1086,20356,4239],{"class":1423},[1086,20358,20359],{"class":1146}," None\n",[1086,20361,20362],{"class":1088,"line":1282},[1086,20363,3390],{"emptyLinePlaceholder":738},[1086,20365,20366,20369,20371,20373,20375,20377,20379,20381,20384,20386,20388,20390],{"class":1088,"line":1288},[1086,20367,20368],{"class":1423},"    with",[1086,20370,7160],{"class":1436},[1086,20372,861],{"class":1146},[1086,20374,19858],{"class":1105},[1086,20376,1398],{"class":1146},[1086,20378,19863],{"class":1401},[1086,20380,1440],{"class":1146},[1086,20382,20383],{"class":1187},"60.0",[1086,20385,1410],{"class":1146},[1086,20387,19873],{"class":1423},[1086,20389,1443],{"class":1436},[1086,20391,1418],{"class":1146},[1086,20393,20394,20397,20399,20401,20403,20405],{"class":1088,"line":2685},[1086,20395,20396],{"class":1436},"        resp ",[1086,20398,1440],{"class":1146},[1086,20400,1443],{"class":1436},[1086,20402,861],{"class":1146},[1086,20404,7165],{"class":1105},[1086,20406,4094],{"class":1146},[1086,20408,20409,20412,20414,20416,20418,20420,20422,20424,20427],{"class":1088,"line":2700},[1086,20410,20411],{"class":1155},"            f",[1086,20413,1159],{"class":1096},[1086,20415,4409],{"class":1187},[1086,20417,19494],{"class":1436},[1086,20419,861],{"class":1146},[1086,20421,19549],{"class":4109},[1086,20423,4423],{"class":1187},[1086,20425,20426],{"class":1096},"/document\"",[1086,20428,1202],{"class":1146},[1086,20430,20431,20434,20436,20438,20440,20443,20445,20447],{"class":1088,"line":3398},[1086,20432,20433],{"class":1401},"            headers",[1086,20435,1440],{"class":1146},[1086,20437,19494],{"class":1436},[1086,20439,861],{"class":1146},[1086,20441,20442],{"class":1105},"_auth_headers",[1086,20444,1398],{"class":1146},[1086,20446,4249],{"class":1105},[1086,20448,20449],{"class":1146},"),\n",[1086,20451,20452,20455,20457,20459,20461,20463,20465,20467,20470,20472,20474,20476,20478,20481,20483],{"class":1088,"line":1715},[1086,20453,20454],{"class":1401},"            files",[1086,20456,1553],{"class":1146},[1086,20458,1159],{"class":1146},[1086,20460,8953],{"class":1096},[1086,20462,1159],{"class":1146},[1086,20464,1133],{"class":1146},[1086,20466,5979],{"class":1146},[1086,20468,20469],{"class":1105},"filename",[1086,20471,1227],{"class":1146},[1086,20473,20293],{"class":1105},[1086,20475,1227],{"class":1146},[1086,20477,1195],{"class":1146},[1086,20479,20480],{"class":1096},"application/pdf",[1086,20482,1159],{"class":1146},[1086,20484,20485],{"class":1146},")},\n",[1086,20487,20488],{"class":1088,"line":3409},[1086,20489,4133],{"class":1146},[1086,20491,20492,20495,20497,20499],{"class":1088,"line":3415},[1086,20493,20494],{"class":1436},"        resp",[1086,20496,861],{"class":1146},[1086,20498,15716],{"class":1105},[1086,20500,1387],{"class":1146},[1086,20502,20503,20505,20507,20509,20511,20513,20515,20517,20519,20521,20523,20525,20527,20529,20531,20533,20535,20537],{"class":1088,"line":3421},[1086,20504,4239],{"class":1423},[1086,20506,4520],{"class":1146},[1086,20508,1159],{"class":1146},[1086,20510,4156],{"class":1096},[1086,20512,1159],{"class":1146},[1086,20514,1133],{"class":1146},[1086,20516,20103],{"class":1436},[1086,20518,861],{"class":1146},[1086,20520,1139],{"class":1105},[1086,20522,6813],{"class":1146},[1086,20524,10812],{"class":1105},[1086,20526,1398],{"class":1146},[1086,20528,1159],{"class":1146},[1086,20530,4156],{"class":1096},[1086,20532,1159],{"class":1146},[1086,20534,1227],{"class":1146},[1086,20536,19571],{"class":1146},[1086,20538,20539],{"class":1146},")}\n",[1086,20541,20542],{"class":1088,"line":3427},[1086,20543,3390],{"emptyLinePlaceholder":738},[1086,20545,20546,20548],{"class":1088,"line":3433},[1086,20547,1376],{"class":1146},[1086,20549,19482],{"class":1092},[1086,20551,20552,20554,20557,20559,20561,20563,20566,20568,20570,20572,20575,20577,20580,20582,20585,20588,20590,20592],{"class":1088,"line":3439},[1086,20553,1392],{"class":1155},[1086,20555,20556],{"class":1105}," add_signature_fields",[1086,20558,1398],{"class":1146},[1086,20560,19494],{"class":1401},[1086,20562,1227],{"class":1146},[1086,20564,20565],{"class":1401}," document_id",[1086,20567,1133],{"class":1146},[1086,20569,1407],{"class":1092},[1086,20571,1227],{"class":1146},[1086,20573,20574],{"class":1401}," fields",[1086,20576,1133],{"class":1146},[1086,20578,20579],{"class":1436}," list",[1086,20581,4340],{"class":1146},[1086,20583,20584],{"class":1092},"dict",[1086,20586,20587],{"class":1146},"])",[1086,20589,1413],{"class":1146},[1086,20591,19501],{"class":1092},[1086,20593,1418],{"class":1146},[1086,20595,20596,20598,20601],{"class":1088,"line":3444},[1086,20597,1424],{"class":1423},[1086,20599,20600],{"class":1427},"Add signature/text fields to an uploaded document.",[1086,20602,1431],{"class":1423},[1086,20604,20605,20607,20609,20611,20613,20615],{"class":1088,"line":3450},[1086,20606,20331],{"class":1436},[1086,20608,1440],{"class":1146},[1086,20610,19520],{"class":1436},[1086,20612,861],{"class":1146},[1086,20614,20340],{"class":1105},[1086,20616,1387],{"class":1146},[1086,20618,20619,20621,20623,20625],{"class":1088,"line":3456},[1086,20620,6474],{"class":1423},[1086,20622,6803],{"class":1146},[1086,20624,4256],{"class":1436},[1086,20626,1418],{"class":1146},[1086,20628,20629,20631],{"class":1088,"line":3462},[1086,20630,4239],{"class":1423},[1086,20632,19434],{"class":1146},[1086,20634,20635],{"class":1088,"line":3467},[1086,20636,3390],{"emptyLinePlaceholder":738},[1086,20638,20639,20641,20643,20645,20647,20649,20651,20653,20655,20657,20659,20661],{"class":1088,"line":3473},[1086,20640,20368],{"class":1423},[1086,20642,7160],{"class":1436},[1086,20644,861],{"class":1146},[1086,20646,19858],{"class":1105},[1086,20648,1398],{"class":1146},[1086,20650,19863],{"class":1401},[1086,20652,1440],{"class":1146},[1086,20654,19868],{"class":1187},[1086,20656,1410],{"class":1146},[1086,20658,19873],{"class":1423},[1086,20660,1443],{"class":1436},[1086,20662,1418],{"class":1146},[1086,20664,20665,20667,20669,20671,20673,20676],{"class":1088,"line":3479},[1086,20666,20396],{"class":1436},[1086,20668,1440],{"class":1146},[1086,20670,1443],{"class":1436},[1086,20672,861],{"class":1146},[1086,20674,20675],{"class":1105},"put",[1086,20677,4094],{"class":1146},[1086,20679,20680,20682,20684,20686,20688,20690,20692,20694,20697,20699,20702,20704,20706],{"class":1088,"line":3485},[1086,20681,20411],{"class":1155},[1086,20683,1159],{"class":1096},[1086,20685,4409],{"class":1187},[1086,20687,19494],{"class":1436},[1086,20689,861],{"class":1146},[1086,20691,19549],{"class":4109},[1086,20693,4423],{"class":1187},[1086,20695,20696],{"class":1096},"/document/",[1086,20698,4409],{"class":1187},[1086,20700,20701],{"class":1105},"document_id",[1086,20703,4423],{"class":1187},[1086,20705,1159],{"class":1096},[1086,20707,1202],{"class":1146},[1086,20709,20710,20712,20715,20717,20719,20721,20723,20725,20727,20729,20731,20733,20735,20737,20740,20742],{"class":1088,"line":3491},[1086,20711,20433],{"class":1401},[1086,20713,20714],{"class":1146},"={**",[1086,20716,19494],{"class":1436},[1086,20718,861],{"class":1146},[1086,20720,20442],{"class":1105},[1086,20722,1398],{"class":1146},[1086,20724,4249],{"class":1105},[1086,20726,4179],{"class":1146},[1086,20728,1195],{"class":1146},[1086,20730,20056],{"class":1096},[1086,20732,1159],{"class":1146},[1086,20734,1133],{"class":1146},[1086,20736,1195],{"class":1146},[1086,20738,20739],{"class":1096},"application/json",[1086,20741,1159],{"class":1146},[1086,20743,17375],{"class":1146},[1086,20745,20746,20749,20751,20753,20755,20757,20759,20761],{"class":1088,"line":3497},[1086,20747,20748],{"class":1401},"            json",[1086,20750,1553],{"class":1146},[1086,20752,1159],{"class":1146},[1086,20754,6028],{"class":1096},[1086,20756,1159],{"class":1146},[1086,20758,1133],{"class":1146},[1086,20760,20574],{"class":1105},[1086,20762,17375],{"class":1146},[1086,20764,20765],{"class":1088,"line":3503},[1086,20766,4133],{"class":1146},[1086,20768,20769,20771,20773,20775],{"class":1088,"line":3509},[1086,20770,20494],{"class":1436},[1086,20772,861],{"class":1146},[1086,20774,15716],{"class":1105},[1086,20776,1387],{"class":1146},[1086,20778,20779,20781],{"class":1088,"line":3515},[1086,20780,4239],{"class":1423},[1086,20782,19535],{"class":1146},[1086,20784,20785],{"class":1088,"line":3520},[1086,20786,3390],{"emptyLinePlaceholder":738},[1086,20788,20789,20791],{"class":1088,"line":3526},[1086,20790,1376],{"class":1146},[1086,20792,19482],{"class":1092},[1086,20794,20795,20797,20800,20802,20804,20806,20808,20810,20812,20814,20816,20818,20820],{"class":1088,"line":3531},[1086,20796,1392],{"class":1155},[1086,20798,20799],{"class":1105}," download_signed_document",[1086,20801,1398],{"class":1146},[1086,20803,19494],{"class":1401},[1086,20805,1227],{"class":1146},[1086,20807,20565],{"class":1401},[1086,20809,1133],{"class":1146},[1086,20811,1407],{"class":1092},[1086,20813,1410],{"class":1146},[1086,20815,1413],{"class":1146},[1086,20817,20298],{"class":1092},[1086,20819,11883],{"class":1146},[1086,20821,19794],{"class":1146},[1086,20823,20824,20826,20829],{"class":1088,"line":3537},[1086,20825,1424],{"class":1423},[1086,20827,20828],{"class":1427},"Download the signed (collapsed) PDF.",[1086,20830,1431],{"class":1423},[1086,20832,20833,20835,20837,20839,20841,20843],{"class":1088,"line":3543},[1086,20834,20331],{"class":1436},[1086,20836,1440],{"class":1146},[1086,20838,19520],{"class":1436},[1086,20840,861],{"class":1146},[1086,20842,20340],{"class":1105},[1086,20844,1387],{"class":1146},[1086,20846,20847,20849,20851,20853],{"class":1088,"line":3549},[1086,20848,6474],{"class":1423},[1086,20850,6803],{"class":1146},[1086,20852,4256],{"class":1436},[1086,20854,1418],{"class":1146},[1086,20856,20857,20859],{"class":1088,"line":3555},[1086,20858,4239],{"class":1423},[1086,20860,20359],{"class":1146},[1086,20862,20863],{"class":1088,"line":3561},[1086,20864,3390],{"emptyLinePlaceholder":738},[1086,20866,20867,20869,20871,20873,20875,20877,20879,20881,20883,20885,20887,20889],{"class":1088,"line":3567},[1086,20868,20368],{"class":1423},[1086,20870,7160],{"class":1436},[1086,20872,861],{"class":1146},[1086,20874,19858],{"class":1105},[1086,20876,1398],{"class":1146},[1086,20878,19863],{"class":1401},[1086,20880,1440],{"class":1146},[1086,20882,20383],{"class":1187},[1086,20884,1410],{"class":1146},[1086,20886,19873],{"class":1423},[1086,20888,1443],{"class":1436},[1086,20890,1418],{"class":1146},[1086,20892,20893,20895,20897,20899,20901,20903],{"class":1088,"line":17749},[1086,20894,20396],{"class":1436},[1086,20896,1440],{"class":1146},[1086,20898,1443],{"class":1436},[1086,20900,861],{"class":1146},[1086,20902,10812],{"class":1105},[1086,20904,4094],{"class":1146},[1086,20906,20907,20909,20911,20913,20915,20917,20919,20921,20923,20925,20927,20929,20932],{"class":1088,"line":19843},[1086,20908,20411],{"class":1155},[1086,20910,1159],{"class":1096},[1086,20912,4409],{"class":1187},[1086,20914,19494],{"class":1436},[1086,20916,861],{"class":1146},[1086,20918,19549],{"class":4109},[1086,20920,4423],{"class":1187},[1086,20922,20696],{"class":1096},[1086,20924,4409],{"class":1187},[1086,20926,20701],{"class":1105},[1086,20928,4423],{"class":1187},[1086,20930,20931],{"class":1096},"/download\"",[1086,20933,1202],{"class":1146},[1086,20935,20936,20938,20940,20942,20944,20946,20948,20950],{"class":1088,"line":19848},[1086,20937,20433],{"class":1401},[1086,20939,1440],{"class":1146},[1086,20941,19494],{"class":1436},[1086,20943,861],{"class":1146},[1086,20945,20442],{"class":1105},[1086,20947,1398],{"class":1146},[1086,20949,4249],{"class":1105},[1086,20951,20449],{"class":1146},[1086,20953,20954,20957,20959,20961,20963,20965,20967,20969,20972,20974],{"class":1088,"line":19880},[1086,20955,20956],{"class":1401},"            params",[1086,20958,1553],{"class":1146},[1086,20960,1159],{"class":1146},[1086,20962,12011],{"class":1096},[1086,20964,1159],{"class":1146},[1086,20966,1133],{"class":1146},[1086,20968,1195],{"class":1146},[1086,20970,20971],{"class":1096},"collapsed",[1086,20973,1159],{"class":1146},[1086,20975,17375],{"class":1146},[1086,20977,20978],{"class":1088,"line":19896},[1086,20979,4133],{"class":1146},[1086,20981,20982,20984,20986,20988],{"class":1088,"line":19919},[1086,20983,20494],{"class":1436},[1086,20985,861],{"class":1146},[1086,20987,15716],{"class":1105},[1086,20989,1387],{"class":1146},[1086,20991,20992,20994,20996,20998],{"class":1088,"line":19927},[1086,20993,4239],{"class":1423},[1086,20995,20103],{"class":1436},[1086,20997,861],{"class":1146},[1086,20999,21000],{"class":4109},"content\n",[1074,21002,21004],{"id":21003},"sending-invites","Sending invites",[842,21006,21007],{},"SignNow supports two invite types:",[958,21009,21010,21016],{},[961,21011,21012,21015],{},[996,21013,21014],{},"Freeform invites"," - The signer places their signature wherever they want",[961,21017,21018,21021],{},[996,21019,21020],{},"Role-based invites"," - Signature fields are pre-positioned, and signers fill specific roles",[842,21023,21024],{},"We used role-based invites because we wanted to control exactly where the signature appears on each document:",[1013,21026,21028],{"className":1368,"code":21027,"filename":19317,"language":1250,"meta":728,"style":728},"@classmethod\ndef send_role_based_invite(\n    cls,\n    document_id: str,\n    signers: list[dict],\n    from_email: str,\n    subject: str = \"\",\n    message: str = \"\",\n) -> dict | None:\n    \"\"\"\n    Send a role-based invite with specific field assignments.\n    Each signer dict: {\"email\": ..., \"role\": ..., \"role_id\": ..., \"order\": ...}\n    \"\"\"\n    token = cls._get_access_token()\n    if not token:\n        return None\n\n    payload = {\"to\": signers, \"from\": from_email}\n    if subject:\n        payload[\"subject\"] = subject\n    if message:\n        payload[\"message\"] = message\n\n    with httpx.Client(timeout=30.0) as client:\n        resp = client.post(\n            f\"{cls._base_url}/document/{document_id}/invite\",\n            headers={**cls._auth_headers(token), \"Content-Type\": \"application/json\"},\n            json=payload,\n        )\n        resp.raise_for_status()\n        return resp.json()\n",[895,21029,21030,21036,21045,21052,21063,21078,21089,21104,21119,21131,21136,21141,21146,21150,21164,21174,21180,21184,21220,21229,21250,21259,21278,21282,21308,21322,21351,21385,21396,21400,21410],{"__ignoreMap":728},[1086,21031,21032,21034],{"class":1088,"line":1089},[1086,21033,1376],{"class":1146},[1086,21035,19482],{"class":1092},[1086,21037,21038,21040,21043],{"class":1088,"line":729},[1086,21039,1392],{"class":1155},[1086,21041,21042],{"class":1105}," send_role_based_invite",[1086,21044,4094],{"class":1146},[1086,21046,21047,21050],{"class":1088,"line":1112},[1086,21048,21049],{"class":1401},"    cls",[1086,21051,1202],{"class":1146},[1086,21053,21054,21057,21059,21061],{"class":1088,"line":1181},[1086,21055,21056],{"class":1401},"    document_id",[1086,21058,1133],{"class":1146},[1086,21060,1407],{"class":1092},[1086,21062,1202],{"class":1146},[1086,21064,21065,21068,21070,21072,21074,21076],{"class":1088,"line":1205},[1086,21066,21067],{"class":1401},"    signers",[1086,21069,1133],{"class":1146},[1086,21071,20579],{"class":1436},[1086,21073,4340],{"class":1146},[1086,21075,20584],{"class":1092},[1086,21077,9297],{"class":1146},[1086,21079,21080,21083,21085,21087],{"class":1088,"line":1276},[1086,21081,21082],{"class":1401},"    from_email",[1086,21084,1133],{"class":1146},[1086,21086,1407],{"class":1092},[1086,21088,1202],{"class":1146},[1086,21090,21091,21094,21096,21098,21100,21102],{"class":1088,"line":1282},[1086,21092,21093],{"class":1401},"    subject",[1086,21095,1133],{"class":1146},[1086,21097,1407],{"class":1092},[1086,21099,19552],{"class":1146},[1086,21101,19571],{"class":1146},[1086,21103,1202],{"class":1146},[1086,21105,21106,21109,21111,21113,21115,21117],{"class":1088,"line":1288},[1086,21107,21108],{"class":1401},"    message",[1086,21110,1133],{"class":1146},[1086,21112,1407],{"class":1092},[1086,21114,19552],{"class":1146},[1086,21116,19571],{"class":1146},[1086,21118,1202],{"class":1146},[1086,21120,21121,21123,21125,21127,21129],{"class":1088,"line":2685},[1086,21122,1410],{"class":1146},[1086,21124,1413],{"class":1146},[1086,21126,1518],{"class":1092},[1086,21128,11883],{"class":1146},[1086,21130,19794],{"class":1146},[1086,21132,21133],{"class":1088,"line":2700},[1086,21134,21135],{"class":1423},"    \"\"\"\n",[1086,21137,21138],{"class":1088,"line":3398},[1086,21139,21140],{"class":1427},"    Send a role-based invite with specific field assignments.\n",[1086,21142,21143],{"class":1088,"line":1715},[1086,21144,21145],{"class":1427},"    Each signer dict: {\"email\": ..., \"role\": ..., \"role_id\": ..., \"order\": ...}\n",[1086,21147,21148],{"class":1088,"line":3409},[1086,21149,21135],{"class":1423},[1086,21151,21152,21154,21156,21158,21160,21162],{"class":1088,"line":3415},[1086,21153,20331],{"class":1436},[1086,21155,1440],{"class":1146},[1086,21157,19520],{"class":1436},[1086,21159,861],{"class":1146},[1086,21161,20340],{"class":1105},[1086,21163,1387],{"class":1146},[1086,21165,21166,21168,21170,21172],{"class":1088,"line":3421},[1086,21167,6474],{"class":1423},[1086,21169,6803],{"class":1146},[1086,21171,4256],{"class":1436},[1086,21173,1418],{"class":1146},[1086,21175,21176,21178],{"class":1088,"line":3427},[1086,21177,4239],{"class":1423},[1086,21179,20359],{"class":1146},[1086,21181,21182],{"class":1088,"line":3433},[1086,21183,3390],{"emptyLinePlaceholder":738},[1086,21185,21186,21189,21191,21193,21195,21198,21200,21202,21205,21207,21209,21211,21213,21215,21218],{"class":1088,"line":3439},[1086,21187,21188],{"class":1436},"    payload ",[1086,21190,1440],{"class":1146},[1086,21192,4520],{"class":1146},[1086,21194,1159],{"class":1146},[1086,21196,21197],{"class":1096},"to",[1086,21199,1159],{"class":1146},[1086,21201,1133],{"class":1146},[1086,21203,21204],{"class":1436}," signers",[1086,21206,1227],{"class":1146},[1086,21208,1195],{"class":1146},[1086,21210,15570],{"class":1096},[1086,21212,1159],{"class":1146},[1086,21214,1133],{"class":1146},[1086,21216,21217],{"class":1436}," from_email",[1086,21219,1291],{"class":1146},[1086,21221,21222,21224,21227],{"class":1088,"line":3444},[1086,21223,6474],{"class":1423},[1086,21225,21226],{"class":1436}," subject",[1086,21228,1418],{"class":1146},[1086,21230,21231,21234,21236,21238,21241,21243,21245,21247],{"class":1088,"line":3450},[1086,21232,21233],{"class":1436},"        payload",[1086,21235,4340],{"class":1146},[1086,21237,1159],{"class":1146},[1086,21239,21240],{"class":1096},"subject",[1086,21242,1159],{"class":1146},[1086,21244,4420],{"class":1146},[1086,21246,19552],{"class":1146},[1086,21248,21249],{"class":1436}," subject\n",[1086,21251,21252,21254,21257],{"class":1088,"line":3456},[1086,21253,6474],{"class":1423},[1086,21255,21256],{"class":1436}," message",[1086,21258,1418],{"class":1146},[1086,21260,21261,21263,21265,21267,21269,21271,21273,21275],{"class":1088,"line":3462},[1086,21262,21233],{"class":1436},[1086,21264,4340],{"class":1146},[1086,21266,1159],{"class":1146},[1086,21268,15922],{"class":1096},[1086,21270,1159],{"class":1146},[1086,21272,4420],{"class":1146},[1086,21274,19552],{"class":1146},[1086,21276,21277],{"class":1436}," message\n",[1086,21279,21280],{"class":1088,"line":3467},[1086,21281,3390],{"emptyLinePlaceholder":738},[1086,21283,21284,21286,21288,21290,21292,21294,21296,21298,21300,21302,21304,21306],{"class":1088,"line":3473},[1086,21285,20368],{"class":1423},[1086,21287,7160],{"class":1436},[1086,21289,861],{"class":1146},[1086,21291,19858],{"class":1105},[1086,21293,1398],{"class":1146},[1086,21295,19863],{"class":1401},[1086,21297,1440],{"class":1146},[1086,21299,19868],{"class":1187},[1086,21301,1410],{"class":1146},[1086,21303,19873],{"class":1423},[1086,21305,1443],{"class":1436},[1086,21307,1418],{"class":1146},[1086,21309,21310,21312,21314,21316,21318,21320],{"class":1088,"line":3479},[1086,21311,20396],{"class":1436},[1086,21313,1440],{"class":1146},[1086,21315,1443],{"class":1436},[1086,21317,861],{"class":1146},[1086,21319,7165],{"class":1105},[1086,21321,4094],{"class":1146},[1086,21323,21324,21326,21328,21330,21332,21334,21336,21338,21340,21342,21344,21346,21349],{"class":1088,"line":3485},[1086,21325,20411],{"class":1155},[1086,21327,1159],{"class":1096},[1086,21329,4409],{"class":1187},[1086,21331,19494],{"class":1436},[1086,21333,861],{"class":1146},[1086,21335,19549],{"class":4109},[1086,21337,4423],{"class":1187},[1086,21339,20696],{"class":1096},[1086,21341,4409],{"class":1187},[1086,21343,20701],{"class":1105},[1086,21345,4423],{"class":1187},[1086,21347,21348],{"class":1096},"/invite\"",[1086,21350,1202],{"class":1146},[1086,21352,21353,21355,21357,21359,21361,21363,21365,21367,21369,21371,21373,21375,21377,21379,21381,21383],{"class":1088,"line":3491},[1086,21354,20433],{"class":1401},[1086,21356,20714],{"class":1146},[1086,21358,19494],{"class":1436},[1086,21360,861],{"class":1146},[1086,21362,20442],{"class":1105},[1086,21364,1398],{"class":1146},[1086,21366,4249],{"class":1105},[1086,21368,4179],{"class":1146},[1086,21370,1195],{"class":1146},[1086,21372,20056],{"class":1096},[1086,21374,1159],{"class":1146},[1086,21376,1133],{"class":1146},[1086,21378,1195],{"class":1146},[1086,21380,20739],{"class":1096},[1086,21382,1159],{"class":1146},[1086,21384,17375],{"class":1146},[1086,21386,21387,21389,21391,21394],{"class":1088,"line":3497},[1086,21388,20748],{"class":1401},[1086,21390,1440],{"class":1146},[1086,21392,21393],{"class":1105},"payload",[1086,21395,1202],{"class":1146},[1086,21397,21398],{"class":1088,"line":3503},[1086,21399,4133],{"class":1146},[1086,21401,21402,21404,21406,21408],{"class":1088,"line":3509},[1086,21403,20494],{"class":1436},[1086,21405,861],{"class":1146},[1086,21407,15716],{"class":1105},[1086,21409,1387],{"class":1146},[1086,21411,21412,21414,21416,21418,21420],{"class":1088,"line":3515},[1086,21413,4239],{"class":1423},[1086,21415,20103],{"class":1436},[1086,21417,861],{"class":1146},[1086,21419,1139],{"class":1105},[1086,21421,1387],{"class":1146},[1074,21423,21425],{"id":21424},"webhook-registration","Webhook registration",[842,21427,21428],{},"To get notified when a document is signed, we register a webhook for each document:",[1013,21430,21432],{"className":1368,"code":21431,"filename":19317,"language":1250,"meta":728,"style":728},"@classmethod\ndef register_webhook(cls, event: str, entity_id: str, callback_url: str) -> bool:\n    \"\"\"Register a webhook callback for a specific event on a document.\"\"\"\n    token = cls._get_access_token()\n    if not token:\n        return False\n\n    payload = {\n        \"event\": event,\n        \"entity_id\": entity_id,\n        \"action\": \"callback\",\n        \"attributes\": {\n            \"callback\": callback_url,\n            \"use_tls_12\": True,\n        },\n    }\n\n    webhook_secret = getattr(settings, \"SIGNNOW_WEBHOOK_SECRET\", \"\")\n    if webhook_secret:\n        payload[\"attributes\"][\"secret_key\"] = webhook_secret\n\n    with httpx.Client(timeout=30.0) as client:\n        resp = client.post(\n            f\"{cls._base_url}/v2/events\",\n            headers={**cls._auth_headers(token), \"Content-Type\": \"application/json\"},\n            json=payload,\n        )\n        resp.raise_for_status()\n        return True\n",[895,21433,21434,21440,21486,21495,21509,21519,21525,21529,21537,21552,21567,21586,21599,21614,21628,21633,21637,21641,21668,21677,21705,21709,21735,21749,21770,21804,21814,21818,21828],{"__ignoreMap":728},[1086,21435,21436,21438],{"class":1088,"line":1089},[1086,21437,1376],{"class":1146},[1086,21439,19482],{"class":1092},[1086,21441,21442,21444,21447,21449,21451,21453,21456,21458,21460,21462,21465,21467,21469,21471,21474,21476,21478,21480,21482,21484],{"class":1088,"line":729},[1086,21443,1392],{"class":1155},[1086,21445,21446],{"class":1105}," register_webhook",[1086,21448,1398],{"class":1146},[1086,21450,19494],{"class":1401},[1086,21452,1227],{"class":1146},[1086,21454,21455],{"class":1401}," event",[1086,21457,1133],{"class":1146},[1086,21459,1407],{"class":1092},[1086,21461,1227],{"class":1146},[1086,21463,21464],{"class":1401}," entity_id",[1086,21466,1133],{"class":1146},[1086,21468,1407],{"class":1092},[1086,21470,1227],{"class":1146},[1086,21472,21473],{"class":1401}," callback_url",[1086,21475,1133],{"class":1146},[1086,21477,1407],{"class":1092},[1086,21479,1410],{"class":1146},[1086,21481,1413],{"class":1146},[1086,21483,19501],{"class":1092},[1086,21485,1418],{"class":1146},[1086,21487,21488,21490,21493],{"class":1088,"line":1112},[1086,21489,1424],{"class":1423},[1086,21491,21492],{"class":1427},"Register a webhook callback for a specific event on a document.",[1086,21494,1431],{"class":1423},[1086,21496,21497,21499,21501,21503,21505,21507],{"class":1088,"line":1181},[1086,21498,20331],{"class":1436},[1086,21500,1440],{"class":1146},[1086,21502,19520],{"class":1436},[1086,21504,861],{"class":1146},[1086,21506,20340],{"class":1105},[1086,21508,1387],{"class":1146},[1086,21510,21511,21513,21515,21517],{"class":1088,"line":1205},[1086,21512,6474],{"class":1423},[1086,21514,6803],{"class":1146},[1086,21516,4256],{"class":1436},[1086,21518,1418],{"class":1146},[1086,21520,21521,21523],{"class":1088,"line":1276},[1086,21522,4239],{"class":1423},[1086,21524,19434],{"class":1146},[1086,21526,21527],{"class":1088,"line":1282},[1086,21528,3390],{"emptyLinePlaceholder":738},[1086,21530,21531,21533,21535],{"class":1088,"line":1288},[1086,21532,21188],{"class":1436},[1086,21534,1440],{"class":1146},[1086,21536,1164],{"class":1146},[1086,21538,21539,21541,21544,21546,21548,21550],{"class":1088,"line":2685},[1086,21540,6046],{"class":1146},[1086,21542,21543],{"class":1096},"event",[1086,21545,1159],{"class":1146},[1086,21547,1133],{"class":1146},[1086,21549,21455],{"class":1436},[1086,21551,1202],{"class":1146},[1086,21553,21554,21556,21559,21561,21563,21565],{"class":1088,"line":2700},[1086,21555,6046],{"class":1146},[1086,21557,21558],{"class":1096},"entity_id",[1086,21560,1159],{"class":1146},[1086,21562,1133],{"class":1146},[1086,21564,21464],{"class":1436},[1086,21566,1202],{"class":1146},[1086,21568,21569,21571,21573,21575,21577,21579,21582,21584],{"class":1088,"line":3398},[1086,21570,6046],{"class":1146},[1086,21572,10905],{"class":1096},[1086,21574,1159],{"class":1146},[1086,21576,1133],{"class":1146},[1086,21578,1195],{"class":1146},[1086,21580,21581],{"class":1096},"callback",[1086,21583,1159],{"class":1146},[1086,21585,1202],{"class":1146},[1086,21587,21588,21590,21593,21595,21597],{"class":1088,"line":1715},[1086,21589,6046],{"class":1146},[1086,21591,21592],{"class":1096},"attributes",[1086,21594,1159],{"class":1146},[1086,21596,1133],{"class":1146},[1086,21598,1164],{"class":1146},[1086,21600,21601,21604,21606,21608,21610,21612],{"class":1088,"line":3409},[1086,21602,21603],{"class":1146},"            \"",[1086,21605,21581],{"class":1096},[1086,21607,1159],{"class":1146},[1086,21609,1133],{"class":1146},[1086,21611,21473],{"class":1436},[1086,21613,1202],{"class":1146},[1086,21615,21616,21618,21621,21623,21625],{"class":1088,"line":3415},[1086,21617,21603],{"class":1146},[1086,21619,21620],{"class":1096},"use_tls_12",[1086,21622,1159],{"class":1146},[1086,21624,1133],{"class":1146},[1086,21626,21627],{"class":1146}," True,\n",[1086,21629,21630],{"class":1088,"line":3421},[1086,21631,21632],{"class":1146},"        },\n",[1086,21634,21635],{"class":1088,"line":3427},[1086,21636,1279],{"class":1146},[1086,21638,21639],{"class":1088,"line":3433},[1086,21640,3390],{"emptyLinePlaceholder":738},[1086,21642,21643,21646,21648,21650,21652,21654,21656,21658,21660,21662,21664,21666],{"class":1088,"line":3439},[1086,21644,21645],{"class":1436},"    webhook_secret ",[1086,21647,1440],{"class":1146},[1086,21649,6441],{"class":1105},[1086,21651,1398],{"class":1146},[1086,21653,4104],{"class":1105},[1086,21655,1227],{"class":1146},[1086,21657,1195],{"class":1146},[1086,21659,19061],{"class":1096},[1086,21661,1159],{"class":1146},[1086,21663,1227],{"class":1146},[1086,21665,19571],{"class":1146},[1086,21667,1455],{"class":1146},[1086,21669,21670,21672,21675],{"class":1088,"line":3444},[1086,21671,6474],{"class":1423},[1086,21673,21674],{"class":1436}," webhook_secret",[1086,21676,1418],{"class":1146},[1086,21678,21679,21681,21683,21685,21687,21689,21691,21693,21696,21698,21700,21702],{"class":1088,"line":3450},[1086,21680,21233],{"class":1436},[1086,21682,4340],{"class":1146},[1086,21684,1159],{"class":1146},[1086,21686,21592],{"class":1096},[1086,21688,1159],{"class":1146},[1086,21690,10878],{"class":1146},[1086,21692,1159],{"class":1146},[1086,21694,21695],{"class":1096},"secret_key",[1086,21697,1159],{"class":1146},[1086,21699,4420],{"class":1146},[1086,21701,19552],{"class":1146},[1086,21703,21704],{"class":1436}," webhook_secret\n",[1086,21706,21707],{"class":1088,"line":3456},[1086,21708,3390],{"emptyLinePlaceholder":738},[1086,21710,21711,21713,21715,21717,21719,21721,21723,21725,21727,21729,21731,21733],{"class":1088,"line":3462},[1086,21712,20368],{"class":1423},[1086,21714,7160],{"class":1436},[1086,21716,861],{"class":1146},[1086,21718,19858],{"class":1105},[1086,21720,1398],{"class":1146},[1086,21722,19863],{"class":1401},[1086,21724,1440],{"class":1146},[1086,21726,19868],{"class":1187},[1086,21728,1410],{"class":1146},[1086,21730,19873],{"class":1423},[1086,21732,1443],{"class":1436},[1086,21734,1418],{"class":1146},[1086,21736,21737,21739,21741,21743,21745,21747],{"class":1088,"line":3467},[1086,21738,20396],{"class":1436},[1086,21740,1440],{"class":1146},[1086,21742,1443],{"class":1436},[1086,21744,861],{"class":1146},[1086,21746,7165],{"class":1105},[1086,21748,4094],{"class":1146},[1086,21750,21751,21753,21755,21757,21759,21761,21763,21765,21768],{"class":1088,"line":3473},[1086,21752,20411],{"class":1155},[1086,21754,1159],{"class":1096},[1086,21756,4409],{"class":1187},[1086,21758,19494],{"class":1436},[1086,21760,861],{"class":1146},[1086,21762,19549],{"class":4109},[1086,21764,4423],{"class":1187},[1086,21766,21767],{"class":1096},"/v2/events\"",[1086,21769,1202],{"class":1146},[1086,21771,21772,21774,21776,21778,21780,21782,21784,21786,21788,21790,21792,21794,21796,21798,21800,21802],{"class":1088,"line":3479},[1086,21773,20433],{"class":1401},[1086,21775,20714],{"class":1146},[1086,21777,19494],{"class":1436},[1086,21779,861],{"class":1146},[1086,21781,20442],{"class":1105},[1086,21783,1398],{"class":1146},[1086,21785,4249],{"class":1105},[1086,21787,4179],{"class":1146},[1086,21789,1195],{"class":1146},[1086,21791,20056],{"class":1096},[1086,21793,1159],{"class":1146},[1086,21795,1133],{"class":1146},[1086,21797,1195],{"class":1146},[1086,21799,20739],{"class":1096},[1086,21801,1159],{"class":1146},[1086,21803,17375],{"class":1146},[1086,21805,21806,21808,21810,21812],{"class":1088,"line":3485},[1086,21807,20748],{"class":1401},[1086,21809,1440],{"class":1146},[1086,21811,21393],{"class":1105},[1086,21813,1202],{"class":1146},[1086,21815,21816],{"class":1088,"line":3491},[1086,21817,4133],{"class":1146},[1086,21819,21820,21822,21824,21826],{"class":1088,"line":3497},[1086,21821,20494],{"class":1436},[1086,21823,861],{"class":1146},[1086,21825,15716],{"class":1105},[1086,21827,1387],{"class":1146},[1086,21829,21830,21832],{"class":1088,"line":3503},[1086,21831,4239],{"class":1423},[1086,21833,19535],{"class":1146},[863,21835,21837],{"id":21836},"step-3-track-document-state-with-a-django-model","Step 3: Track document state with a Django model",[842,21839,21840,21841,21843,21844,21847],{},"We need to track each document's journey through the signing pipeline. The ",[895,21842,18952],{}," model uses Django's ",[895,21845,21846],{},"GenericForeignKey"," so it can be attached to any model in the system - a deposit confirmation, an NDA, a beta testing agreement, etc.",[1013,21849,21852],{"className":1368,"code":21850,"filename":21851,"language":1250,"meta":728,"style":728},"from django.contrib.contenttypes.fields import GenericForeignKey\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.db import models\n\n\nclass SignableDocument(models.Model):\n    \"\"\"Tracks a document uploaded to SignNow for e-signature.\"\"\"\n\n    class Status(models.TextChoices):\n        PENDING = \"pending\", \"Pending Upload\"\n        UPLOADED = \"uploaded\", \"Uploaded to SignNow\"\n        INVITE_SENT = \"invite_sent\", \"Invite Sent\"\n        SIGNED = \"signed\", \"Signed\"\n        DOWNLOADED = \"downloaded\", \"Signed PDF Downloaded\"\n        FAILED = \"failed\", \"Failed\"\n\n    # Generic relation - attach to any Django model\n    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)\n    object_id = models.PositiveIntegerField()\n    source_document = GenericForeignKey(\"content_type\", \"object_id\")\n\n    # SignNow tracking\n    signnow_document_id = models.CharField(max_length=64, blank=True, db_index=True)\n    signnow_invite_id = models.CharField(max_length=64, blank=True)\n\n    # Signer info\n    signer_email = models.EmailField()\n    signer_name = models.CharField(max_length=255, blank=True)\n\n    # Status & errors\n    status = models.CharField(max_length=16, choices=Status.choices, default=Status.PENDING)\n    error_message = models.TextField(blank=True)\n\n    # Files\n    original_pdf_name = models.CharField(max_length=512, blank=True)\n    signed_pdf = models.FileField(upload_to=\"signnow/signed/%Y/%m/\", blank=True)\n\n    # Timestamps\n    uploaded_at = models.DateTimeField(null=True, blank=True)\n    invite_sent_at = models.DateTimeField(null=True, blank=True)\n    signed_at = models.DateTimeField(null=True, blank=True)\n    downloaded_at = models.DateTimeField(null=True, blank=True)\n","apps/signnow/models.py",[895,21853,21854,21880,21904,21920,21924,21928,21946,21955,21959,21978,22001,22024,22047,22070,22093,22116,22120,22125,22160,22176,22206,22210,22215,22253,22280,22284,22289,22305,22333,22337,22342,22394,22415,22419,22424,22452,22486,22490,22495,22520,22543,22566],{"__ignoreMap":728},[1086,21855,21856,21858,21860,21862,21865,21867,21870,21872,21875,21877],{"class":1088,"line":1089},[1086,21857,15570],{"class":1423},[1086,21859,19332],{"class":1436},[1086,21861,861],{"class":1146},[1086,21863,21864],{"class":1436},"contrib",[1086,21866,861],{"class":1146},[1086,21868,21869],{"class":1436},"contenttypes",[1086,21871,861],{"class":1146},[1086,21873,21874],{"class":1436},"fields ",[1086,21876,6503],{"class":1423},[1086,21878,21879],{"class":1436}," GenericForeignKey\n",[1086,21881,21882,21884,21886,21888,21890,21892,21894,21896,21899,21901],{"class":1088,"line":729},[1086,21883,15570],{"class":1423},[1086,21885,19332],{"class":1436},[1086,21887,861],{"class":1146},[1086,21889,21864],{"class":1436},[1086,21891,861],{"class":1146},[1086,21893,21869],{"class":1436},[1086,21895,861],{"class":1146},[1086,21897,21898],{"class":1436},"models ",[1086,21900,6503],{"class":1423},[1086,21902,21903],{"class":1436}," ContentType\n",[1086,21905,21906,21908,21910,21912,21915,21917],{"class":1088,"line":1112},[1086,21907,15570],{"class":1423},[1086,21909,19332],{"class":1436},[1086,21911,861],{"class":1146},[1086,21913,21914],{"class":1436},"db ",[1086,21916,6503],{"class":1423},[1086,21918,21919],{"class":1436}," models\n",[1086,21921,21922],{"class":1088,"line":1181},[1086,21923,3390],{"emptyLinePlaceholder":738},[1086,21925,21926],{"class":1088,"line":1205},[1086,21927,3390],{"emptyLinePlaceholder":738},[1086,21929,21930,21932,21935,21937,21940,21942,21944],{"class":1088,"line":1276},[1086,21931,4036],{"class":1155},[1086,21933,21934],{"class":1092}," SignableDocument",[1086,21936,1398],{"class":1146},[1086,21938,21939],{"class":1092},"models",[1086,21941,861],{"class":1146},[1086,21943,11370],{"class":1092},[1086,21945,4047],{"class":1146},[1086,21947,21948,21950,21953],{"class":1088,"line":1282},[1086,21949,1424],{"class":1423},[1086,21951,21952],{"class":1427},"Tracks a document uploaded to SignNow for e-signature.",[1086,21954,1431],{"class":1423},[1086,21956,21957],{"class":1088,"line":1288},[1086,21958,3390],{"emptyLinePlaceholder":738},[1086,21960,21961,21964,21967,21969,21971,21973,21976],{"class":1088,"line":2685},[1086,21962,21963],{"class":1155},"    class",[1086,21965,21966],{"class":1092}," Status",[1086,21968,1398],{"class":1146},[1086,21970,21939],{"class":1092},[1086,21972,861],{"class":1146},[1086,21974,21975],{"class":1092},"TextChoices",[1086,21977,4047],{"class":1146},[1086,21979,21980,21983,21985,21987,21990,21992,21994,21996,21999],{"class":1088,"line":2700},[1086,21981,21982],{"class":1436},"        PENDING ",[1086,21984,1440],{"class":1146},[1086,21986,1195],{"class":1146},[1086,21988,21989],{"class":1096},"pending",[1086,21991,1159],{"class":1146},[1086,21993,1227],{"class":1146},[1086,21995,1195],{"class":1146},[1086,21997,21998],{"class":1096},"Pending Upload",[1086,22000,4441],{"class":1146},[1086,22002,22003,22006,22008,22010,22013,22015,22017,22019,22022],{"class":1088,"line":3398},[1086,22004,22005],{"class":1436},"        UPLOADED ",[1086,22007,1440],{"class":1146},[1086,22009,1195],{"class":1146},[1086,22011,22012],{"class":1096},"uploaded",[1086,22014,1159],{"class":1146},[1086,22016,1227],{"class":1146},[1086,22018,1195],{"class":1146},[1086,22020,22021],{"class":1096},"Uploaded to SignNow",[1086,22023,4441],{"class":1146},[1086,22025,22026,22029,22031,22033,22036,22038,22040,22042,22045],{"class":1088,"line":1715},[1086,22027,22028],{"class":1436},"        INVITE_SENT ",[1086,22030,1440],{"class":1146},[1086,22032,1195],{"class":1146},[1086,22034,22035],{"class":1096},"invite_sent",[1086,22037,1159],{"class":1146},[1086,22039,1227],{"class":1146},[1086,22041,1195],{"class":1146},[1086,22043,22044],{"class":1096},"Invite Sent",[1086,22046,4441],{"class":1146},[1086,22048,22049,22052,22054,22056,22059,22061,22063,22065,22068],{"class":1088,"line":3409},[1086,22050,22051],{"class":1436},"        SIGNED ",[1086,22053,1440],{"class":1146},[1086,22055,1195],{"class":1146},[1086,22057,22058],{"class":1096},"signed",[1086,22060,1159],{"class":1146},[1086,22062,1227],{"class":1146},[1086,22064,1195],{"class":1146},[1086,22066,22067],{"class":1096},"Signed",[1086,22069,4441],{"class":1146},[1086,22071,22072,22075,22077,22079,22082,22084,22086,22088,22091],{"class":1088,"line":3415},[1086,22073,22074],{"class":1436},"        DOWNLOADED ",[1086,22076,1440],{"class":1146},[1086,22078,1195],{"class":1146},[1086,22080,22081],{"class":1096},"downloaded",[1086,22083,1159],{"class":1146},[1086,22085,1227],{"class":1146},[1086,22087,1195],{"class":1146},[1086,22089,22090],{"class":1096},"Signed PDF Downloaded",[1086,22092,4441],{"class":1146},[1086,22094,22095,22098,22100,22102,22105,22107,22109,22111,22114],{"class":1088,"line":3421},[1086,22096,22097],{"class":1436},"        FAILED ",[1086,22099,1440],{"class":1146},[1086,22101,1195],{"class":1146},[1086,22103,22104],{"class":1096},"failed",[1086,22106,1159],{"class":1146},[1086,22108,1227],{"class":1146},[1086,22110,1195],{"class":1146},[1086,22112,22113],{"class":1096},"Failed",[1086,22115,4441],{"class":1146},[1086,22117,22118],{"class":1088,"line":3427},[1086,22119,3390],{"emptyLinePlaceholder":738},[1086,22121,22122],{"class":1088,"line":3433},[1086,22123,22124],{"class":1427},"    # Generic relation - attach to any Django model\n",[1086,22126,22127,22130,22132,22135,22137,22140,22142,22144,22146,22149,22151,22153,22155,22158],{"class":1088,"line":3439},[1086,22128,22129],{"class":1436},"    content_type ",[1086,22131,1440],{"class":1146},[1086,22133,22134],{"class":1436}," models",[1086,22136,861],{"class":1146},[1086,22138,22139],{"class":1105},"ForeignKey",[1086,22141,1398],{"class":1146},[1086,22143,18956],{"class":1105},[1086,22145,1227],{"class":1146},[1086,22147,22148],{"class":1401}," on_delete",[1086,22150,1440],{"class":1146},[1086,22152,21939],{"class":1105},[1086,22154,861],{"class":1146},[1086,22156,22157],{"class":4109},"CASCADE",[1086,22159,1455],{"class":1146},[1086,22161,22162,22165,22167,22169,22171,22174],{"class":1088,"line":3444},[1086,22163,22164],{"class":1436},"    object_id ",[1086,22166,1440],{"class":1146},[1086,22168,22134],{"class":1436},[1086,22170,861],{"class":1146},[1086,22172,22173],{"class":1105},"PositiveIntegerField",[1086,22175,1387],{"class":1146},[1086,22177,22178,22181,22183,22186,22188,22190,22193,22195,22197,22199,22202,22204],{"class":1088,"line":3450},[1086,22179,22180],{"class":1436},"    source_document ",[1086,22182,1440],{"class":1146},[1086,22184,22185],{"class":1105}," GenericForeignKey",[1086,22187,1398],{"class":1146},[1086,22189,1159],{"class":1146},[1086,22191,22192],{"class":1096},"content_type",[1086,22194,1159],{"class":1146},[1086,22196,1227],{"class":1146},[1086,22198,1195],{"class":1146},[1086,22200,22201],{"class":1096},"object_id",[1086,22203,1159],{"class":1146},[1086,22205,1455],{"class":1146},[1086,22207,22208],{"class":1088,"line":3456},[1086,22209,3390],{"emptyLinePlaceholder":738},[1086,22211,22212],{"class":1088,"line":3462},[1086,22213,22214],{"class":1427},"    # SignNow tracking\n",[1086,22216,22217,22220,22222,22224,22226,22229,22231,22234,22236,22239,22241,22244,22247,22250],{"class":1088,"line":3467},[1086,22218,22219],{"class":1436},"    signnow_document_id ",[1086,22221,1440],{"class":1146},[1086,22223,22134],{"class":1436},[1086,22225,861],{"class":1146},[1086,22227,22228],{"class":1105},"CharField",[1086,22230,1398],{"class":1146},[1086,22232,22233],{"class":1401},"max_length",[1086,22235,1440],{"class":1146},[1086,22237,22238],{"class":1187},"64",[1086,22240,1227],{"class":1146},[1086,22242,22243],{"class":1401}," blank",[1086,22245,22246],{"class":1146},"=True,",[1086,22248,22249],{"class":1401}," db_index",[1086,22251,22252],{"class":1146},"=True)\n",[1086,22254,22255,22258,22260,22262,22264,22266,22268,22270,22272,22274,22276,22278],{"class":1088,"line":3473},[1086,22256,22257],{"class":1436},"    signnow_invite_id ",[1086,22259,1440],{"class":1146},[1086,22261,22134],{"class":1436},[1086,22263,861],{"class":1146},[1086,22265,22228],{"class":1105},[1086,22267,1398],{"class":1146},[1086,22269,22233],{"class":1401},[1086,22271,1440],{"class":1146},[1086,22273,22238],{"class":1187},[1086,22275,1227],{"class":1146},[1086,22277,22243],{"class":1401},[1086,22279,22252],{"class":1146},[1086,22281,22282],{"class":1088,"line":3479},[1086,22283,3390],{"emptyLinePlaceholder":738},[1086,22285,22286],{"class":1088,"line":3485},[1086,22287,22288],{"class":1427},"    # Signer info\n",[1086,22290,22291,22294,22296,22298,22300,22303],{"class":1088,"line":3491},[1086,22292,22293],{"class":1436},"    signer_email ",[1086,22295,1440],{"class":1146},[1086,22297,22134],{"class":1436},[1086,22299,861],{"class":1146},[1086,22301,22302],{"class":1105},"EmailField",[1086,22304,1387],{"class":1146},[1086,22306,22307,22310,22312,22314,22316,22318,22320,22322,22324,22327,22329,22331],{"class":1088,"line":3497},[1086,22308,22309],{"class":1436},"    signer_name ",[1086,22311,1440],{"class":1146},[1086,22313,22134],{"class":1436},[1086,22315,861],{"class":1146},[1086,22317,22228],{"class":1105},[1086,22319,1398],{"class":1146},[1086,22321,22233],{"class":1401},[1086,22323,1440],{"class":1146},[1086,22325,22326],{"class":1187},"255",[1086,22328,1227],{"class":1146},[1086,22330,22243],{"class":1401},[1086,22332,22252],{"class":1146},[1086,22334,22335],{"class":1088,"line":3503},[1086,22336,3390],{"emptyLinePlaceholder":738},[1086,22338,22339],{"class":1088,"line":3509},[1086,22340,22341],{"class":1427},"    # Status & errors\n",[1086,22343,22344,22347,22349,22351,22353,22355,22357,22359,22361,22364,22366,22369,22371,22374,22376,22379,22381,22383,22385,22387,22389,22392],{"class":1088,"line":3515},[1086,22345,22346],{"class":1436},"    status ",[1086,22348,1440],{"class":1146},[1086,22350,22134],{"class":1436},[1086,22352,861],{"class":1146},[1086,22354,22228],{"class":1105},[1086,22356,1398],{"class":1146},[1086,22358,22233],{"class":1401},[1086,22360,1440],{"class":1146},[1086,22362,22363],{"class":1187},"16",[1086,22365,1227],{"class":1146},[1086,22367,22368],{"class":1401}," choices",[1086,22370,1440],{"class":1146},[1086,22372,22373],{"class":1105},"Status",[1086,22375,861],{"class":1146},[1086,22377,22378],{"class":4109},"choices",[1086,22380,1227],{"class":1146},[1086,22382,19130],{"class":1401},[1086,22384,1440],{"class":1146},[1086,22386,22373],{"class":1105},[1086,22388,861],{"class":1146},[1086,22390,22391],{"class":4109},"PENDING",[1086,22393,1455],{"class":1146},[1086,22395,22396,22399,22401,22403,22405,22408,22410,22413],{"class":1088,"line":3520},[1086,22397,22398],{"class":1436},"    error_message ",[1086,22400,1440],{"class":1146},[1086,22402,22134],{"class":1436},[1086,22404,861],{"class":1146},[1086,22406,22407],{"class":1105},"TextField",[1086,22409,1398],{"class":1146},[1086,22411,22412],{"class":1401},"blank",[1086,22414,22252],{"class":1146},[1086,22416,22417],{"class":1088,"line":3526},[1086,22418,3390],{"emptyLinePlaceholder":738},[1086,22420,22421],{"class":1088,"line":3531},[1086,22422,22423],{"class":1427},"    # Files\n",[1086,22425,22426,22429,22431,22433,22435,22437,22439,22441,22443,22446,22448,22450],{"class":1088,"line":3537},[1086,22427,22428],{"class":1436},"    original_pdf_name ",[1086,22430,1440],{"class":1146},[1086,22432,22134],{"class":1436},[1086,22434,861],{"class":1146},[1086,22436,22228],{"class":1105},[1086,22438,1398],{"class":1146},[1086,22440,22233],{"class":1401},[1086,22442,1440],{"class":1146},[1086,22444,22445],{"class":1187},"512",[1086,22447,1227],{"class":1146},[1086,22449,22243],{"class":1401},[1086,22451,22252],{"class":1146},[1086,22453,22454,22457,22459,22461,22463,22466,22468,22471,22473,22475,22478,22480,22482,22484],{"class":1088,"line":3543},[1086,22455,22456],{"class":1436},"    signed_pdf ",[1086,22458,1440],{"class":1146},[1086,22460,22134],{"class":1436},[1086,22462,861],{"class":1146},[1086,22464,22465],{"class":1105},"FileField",[1086,22467,1398],{"class":1146},[1086,22469,22470],{"class":1401},"upload_to",[1086,22472,1440],{"class":1146},[1086,22474,1159],{"class":1146},[1086,22476,22477],{"class":1096},"signnow/signed/%Y/%m/",[1086,22479,1159],{"class":1146},[1086,22481,1227],{"class":1146},[1086,22483,22243],{"class":1401},[1086,22485,22252],{"class":1146},[1086,22487,22488],{"class":1088,"line":3549},[1086,22489,3390],{"emptyLinePlaceholder":738},[1086,22491,22492],{"class":1088,"line":3555},[1086,22493,22494],{"class":1427},"    # Timestamps\n",[1086,22496,22497,22500,22502,22504,22506,22509,22511,22514,22516,22518],{"class":1088,"line":3561},[1086,22498,22499],{"class":1436},"    uploaded_at ",[1086,22501,1440],{"class":1146},[1086,22503,22134],{"class":1436},[1086,22505,861],{"class":1146},[1086,22507,22508],{"class":1105},"DateTimeField",[1086,22510,1398],{"class":1146},[1086,22512,22513],{"class":1401},"null",[1086,22515,22246],{"class":1146},[1086,22517,22243],{"class":1401},[1086,22519,22252],{"class":1146},[1086,22521,22522,22525,22527,22529,22531,22533,22535,22537,22539,22541],{"class":1088,"line":3567},[1086,22523,22524],{"class":1436},"    invite_sent_at ",[1086,22526,1440],{"class":1146},[1086,22528,22134],{"class":1436},[1086,22530,861],{"class":1146},[1086,22532,22508],{"class":1105},[1086,22534,1398],{"class":1146},[1086,22536,22513],{"class":1401},[1086,22538,22246],{"class":1146},[1086,22540,22243],{"class":1401},[1086,22542,22252],{"class":1146},[1086,22544,22545,22548,22550,22552,22554,22556,22558,22560,22562,22564],{"class":1088,"line":17749},[1086,22546,22547],{"class":1436},"    signed_at ",[1086,22549,1440],{"class":1146},[1086,22551,22134],{"class":1436},[1086,22553,861],{"class":1146},[1086,22555,22508],{"class":1105},[1086,22557,1398],{"class":1146},[1086,22559,22513],{"class":1401},[1086,22561,22246],{"class":1146},[1086,22563,22243],{"class":1401},[1086,22565,22252],{"class":1146},[1086,22567,22568,22571,22573,22575,22577,22579,22581,22583,22585,22587],{"class":1088,"line":19843},[1086,22569,22570],{"class":1436},"    downloaded_at ",[1086,22572,1440],{"class":1146},[1086,22574,22134],{"class":1436},[1086,22576,861],{"class":1146},[1086,22578,22508],{"class":1105},[1086,22580,1398],{"class":1146},[1086,22582,22513],{"class":1401},[1086,22584,22246],{"class":1146},[1086,22586,22243],{"class":1401},[1086,22588,22252],{"class":1146},[842,22590,22591],{},"The status flow is linear and predictable:",[1013,22593,22596],{"className":22594,"code":22595,"language":1018},[1016],"PENDING → UPLOADED → INVITE_SENT → SIGNED → DOWNLOADED\n                                                ↘ FAILED (at any step)\n",[895,22597,22595],{"__ignoreMap":728},[863,22599,22601],{"id":22600},"step-4-build-the-async-signing-pipeline-with-celery","Step 4: Build the async signing pipeline with Celery",[842,22603,22604],{},"This is where everything comes together. A single Celery task orchestrates the entire signing flow - upload the document, add signature fields, send the invite, and register the webhook:",[1013,22606,22609],{"className":1368,"code":22607,"filename":22608,"language":1250,"meta":728,"style":728},"from celery import shared_task\n\n# Signature field position (calibrate for your PDF layout)\nTESTER_SIGNATURE_FIELD = {\n    \"x\": 350,\n    \"y\": 700,\n    \"width\": 200,\n    \"height\": 50,\n    \"page_number\": 0,\n    \"type\": \"signature\",\n    \"role\": \"Signer 1\",\n    \"required\": True,\n    \"label\": \"Tester Signature\",\n}\n\n\n@shared_task(bind=True, max_retries=3, default_retry_delay=60)\ndef upload_and_send_for_signature(self, signable_document_id: int):\n    \"\"\"Full pipeline: upload PDF → add fields → send invite → register webhook.\"\"\"\n    from signnow.models import SignableDocument\n    from signnow.services.signnow_service import SignNowService\n\n    signable = SignableDocument.objects.select_related(\"content_type\").get(\n        id=signable_document_id\n    )\n\n    try:\n        # 1. Read PDF from storage\n        source = signable.source_document\n        pdf_bytes = source.pdf_file.read()\n        filename = source.pdf_file.name.split(\"/\")[-1]\n\n        # 2. Upload to SignNow\n        if not signable.signnow_document_id:\n            result = SignNowService.upload_document(pdf_bytes, filename)\n            if not result:\n                raise RuntimeError(\"Failed to upload document to SignNow\")\n\n            signable.signnow_document_id = result[\"id\"]\n            signable.status = SignableDocument.Status.UPLOADED\n            signable.uploaded_at = timezone.now()\n            signable.save(update_fields=[\"signnow_document_id\", \"status\", \"uploaded_at\"])\n\n        # 3. Add signature fields\n        SignNowService.add_signature_fields(\n            signable.signnow_document_id,\n            [TESTER_SIGNATURE_FIELD],\n        )\n\n        # 4. Get role_id (assigned when fields were added)\n        doc_data = SignNowService.get_document(signable.signnow_document_id)\n        roles = doc_data.get(\"roles\", [])\n        signer_role = next(r for r in roles if r.get(\"name\") == \"Signer 1\")\n\n        # 5. Send role-based invite\n        signers = [{\n            \"email\": signable.signer_email,\n            \"role\": \"Signer 1\",\n            \"role_id\": signer_role[\"unique_id\"],\n            \"order\": 1,\n        }]\n\n        invite_result = SignNowService.send_role_based_invite(\n            document_id=signable.signnow_document_id,\n            signers=signers,\n            from_email=settings.SIGNNOW_USERNAME,\n        )\n\n        signable.status = SignableDocument.Status.INVITE_SENT\n        signable.invite_sent_at = timezone.now()\n        signable.save(update_fields=[\"status\", \"invite_sent_at\"])\n\n        # 6. Register webhook for document completion\n        callback_url = settings.SIGNNOW_WEBHOOK_CALLBACK_URL\n        if callback_url:\n            SignNowService.register_webhook(\n                event=\"document.complete\",\n                entity_id=signable.signnow_document_id,\n                callback_url=callback_url,\n            )\n\n    except Exception as exc:\n        if self.request.retries >= self.max_retries:\n            signable.status = SignableDocument.Status.FAILED\n            signable.error_message = str(exc)\n            signable.save(update_fields=[\"status\", \"error_message\"])\n            return\n        self.retry(exc=exc)\n","apps/signnow/tasks.py",[895,22610,22611,22623,22627,22632,22641,22657,22673,22689,22705,22721,22740,22759,22772,22791,22795,22799,22803,22836,22859,22868,22883,22904,22908,22941,22951,22955,22959,22966,22971,22986,23008,23046,23050,23055,23070,23094,23105,23123,23127,23150,23171,23192,23234,23238,23243,23255,23265,23275,23279,23283,23288,23312,23340,23394,23398,23403,23413,23432,23450,23475,23491,23496,23500,23515,23530,23542,23558,23563,23568,23591,23611,23642,23647,23653,23668,23677,23689,23706,23722,23735,23740,23745,23761,23790,23812,23833,23864,23870],{"__ignoreMap":728},[1086,22612,22613,22615,22618,22620],{"class":1088,"line":1089},[1086,22614,15570],{"class":1423},[1086,22616,22617],{"class":1436}," celery ",[1086,22619,6503],{"class":1423},[1086,22621,22622],{"class":1436}," shared_task\n",[1086,22624,22625],{"class":1088,"line":729},[1086,22626,3390],{"emptyLinePlaceholder":738},[1086,22628,22629],{"class":1088,"line":1112},[1086,22630,22631],{"class":1427},"# Signature field position (calibrate for your PDF layout)\n",[1086,22633,22634,22637,22639],{"class":1088,"line":1181},[1086,22635,22636],{"class":1436},"TESTER_SIGNATURE_FIELD ",[1086,22638,1440],{"class":1146},[1086,22640,1164],{"class":1146},[1086,22642,22643,22645,22648,22650,22652,22655],{"class":1088,"line":1205},[1086,22644,1169],{"class":1146},[1086,22646,22647],{"class":1096},"x",[1086,22649,1159],{"class":1146},[1086,22651,1133],{"class":1146},[1086,22653,22654],{"class":1187}," 350",[1086,22656,1202],{"class":1146},[1086,22658,22659,22661,22664,22666,22668,22671],{"class":1088,"line":1276},[1086,22660,1169],{"class":1146},[1086,22662,22663],{"class":1096},"y",[1086,22665,1159],{"class":1146},[1086,22667,1133],{"class":1146},[1086,22669,22670],{"class":1187}," 700",[1086,22672,1202],{"class":1146},[1086,22674,22675,22677,22680,22682,22684,22687],{"class":1088,"line":1282},[1086,22676,1169],{"class":1146},[1086,22678,22679],{"class":1096},"width",[1086,22681,1159],{"class":1146},[1086,22683,1133],{"class":1146},[1086,22685,22686],{"class":1187}," 200",[1086,22688,1202],{"class":1146},[1086,22690,22691,22693,22696,22698,22700,22703],{"class":1088,"line":1288},[1086,22692,1169],{"class":1146},[1086,22694,22695],{"class":1096},"height",[1086,22697,1159],{"class":1146},[1086,22699,1133],{"class":1146},[1086,22701,22702],{"class":1187}," 50",[1086,22704,1202],{"class":1146},[1086,22706,22707,22709,22712,22714,22716,22719],{"class":1088,"line":2685},[1086,22708,1169],{"class":1146},[1086,22710,22711],{"class":1096},"page_number",[1086,22713,1159],{"class":1146},[1086,22715,1133],{"class":1146},[1086,22717,22718],{"class":1187}," 0",[1086,22720,1202],{"class":1146},[1086,22722,22723,22725,22727,22729,22731,22733,22736,22738],{"class":1088,"line":2700},[1086,22724,1169],{"class":1146},[1086,22726,12011],{"class":1096},[1086,22728,1159],{"class":1146},[1086,22730,1133],{"class":1146},[1086,22732,1195],{"class":1146},[1086,22734,22735],{"class":1096},"signature",[1086,22737,1159],{"class":1146},[1086,22739,1202],{"class":1146},[1086,22741,22742,22744,22746,22748,22750,22752,22755,22757],{"class":1088,"line":3398},[1086,22743,1169],{"class":1146},[1086,22745,17362],{"class":1096},[1086,22747,1159],{"class":1146},[1086,22749,1133],{"class":1146},[1086,22751,1195],{"class":1146},[1086,22753,22754],{"class":1096},"Signer 1",[1086,22756,1159],{"class":1146},[1086,22758,1202],{"class":1146},[1086,22760,22761,22763,22766,22768,22770],{"class":1088,"line":1715},[1086,22762,1169],{"class":1146},[1086,22764,22765],{"class":1096},"required",[1086,22767,1159],{"class":1146},[1086,22769,1133],{"class":1146},[1086,22771,21627],{"class":1146},[1086,22773,22774,22776,22778,22780,22782,22784,22787,22789],{"class":1088,"line":3409},[1086,22775,1169],{"class":1146},[1086,22777,10840],{"class":1096},[1086,22779,1159],{"class":1146},[1086,22781,1133],{"class":1146},[1086,22783,1195],{"class":1146},[1086,22785,22786],{"class":1096},"Tester Signature",[1086,22788,1159],{"class":1146},[1086,22790,1202],{"class":1146},[1086,22792,22793],{"class":1088,"line":3415},[1086,22794,1291],{"class":1146},[1086,22796,22797],{"class":1088,"line":3421},[1086,22798,3390],{"emptyLinePlaceholder":738},[1086,22800,22801],{"class":1088,"line":3427},[1086,22802,3390],{"emptyLinePlaceholder":738},[1086,22804,22805,22807,22810,22812,22815,22817,22820,22822,22824,22826,22829,22831,22834],{"class":1088,"line":3433},[1086,22806,1376],{"class":1146},[1086,22808,22809],{"class":1105},"shared_task",[1086,22811,1398],{"class":1146},[1086,22813,22814],{"class":1401},"bind",[1086,22816,22246],{"class":1146},[1086,22818,22819],{"class":1401}," max_retries",[1086,22821,1440],{"class":1146},[1086,22823,7948],{"class":1187},[1086,22825,1227],{"class":1146},[1086,22827,22828],{"class":1401}," default_retry_delay",[1086,22830,1440],{"class":1146},[1086,22832,22833],{"class":1187},"60",[1086,22835,1455],{"class":1146},[1086,22837,22838,22840,22843,22845,22847,22849,22852,22854,22857],{"class":1088,"line":3439},[1086,22839,1392],{"class":1155},[1086,22841,22842],{"class":1105}," upload_and_send_for_signature",[1086,22844,1398],{"class":1146},[1086,22846,4074],{"class":4073},[1086,22848,1227],{"class":1146},[1086,22850,22851],{"class":1401}," signable_document_id",[1086,22853,1133],{"class":1146},[1086,22855,22856],{"class":1092}," int",[1086,22858,4047],{"class":1146},[1086,22860,22861,22863,22866],{"class":1088,"line":3444},[1086,22862,1424],{"class":1423},[1086,22864,22865],{"class":1427},"Full pipeline: upload PDF → add fields → send invite → register webhook.",[1086,22867,1431],{"class":1423},[1086,22869,22870,22872,22874,22876,22878,22880],{"class":1088,"line":3450},[1086,22871,6524],{"class":1423},[1086,22873,11956],{"class":1436},[1086,22875,861],{"class":1146},[1086,22877,21898],{"class":1436},[1086,22879,6503],{"class":1423},[1086,22881,22882],{"class":1436}," SignableDocument\n",[1086,22884,22885,22887,22889,22891,22894,22896,22899,22901],{"class":1088,"line":3456},[1086,22886,6524],{"class":1423},[1086,22888,11956],{"class":1436},[1086,22890,861],{"class":1146},[1086,22892,22893],{"class":1436},"services",[1086,22895,861],{"class":1146},[1086,22897,22898],{"class":1436},"signnow_service ",[1086,22900,6503],{"class":1423},[1086,22902,22903],{"class":1436}," SignNowService\n",[1086,22905,22906],{"class":1088,"line":3462},[1086,22907,3390],{"emptyLinePlaceholder":738},[1086,22909,22910,22913,22915,22917,22919,22922,22924,22927,22929,22931,22933,22935,22937,22939],{"class":1088,"line":3467},[1086,22911,22912],{"class":1436},"    signable ",[1086,22914,1440],{"class":1146},[1086,22916,21934],{"class":1436},[1086,22918,861],{"class":1146},[1086,22920,22921],{"class":4109},"objects",[1086,22923,861],{"class":1146},[1086,22925,22926],{"class":1105},"select_related",[1086,22928,1398],{"class":1146},[1086,22930,1159],{"class":1146},[1086,22932,22192],{"class":1096},[1086,22934,1159],{"class":1146},[1086,22936,6786],{"class":1146},[1086,22938,10812],{"class":1105},[1086,22940,4094],{"class":1146},[1086,22942,22943,22946,22948],{"class":1088,"line":3473},[1086,22944,22945],{"class":1401},"        id",[1086,22947,1440],{"class":1146},[1086,22949,22950],{"class":1105},"signable_document_id\n",[1086,22952,22953],{"class":1088,"line":3479},[1086,22954,6219],{"class":1146},[1086,22956,22957],{"class":1088,"line":3485},[1086,22958,3390],{"emptyLinePlaceholder":738},[1086,22960,22961,22964],{"class":1088,"line":3491},[1086,22962,22963],{"class":1423},"    try",[1086,22965,1418],{"class":1146},[1086,22967,22968],{"class":1088,"line":3497},[1086,22969,22970],{"class":1427},"        # 1. Read PDF from storage\n",[1086,22972,22973,22976,22978,22981,22983],{"class":1088,"line":3503},[1086,22974,22975],{"class":1436},"        source ",[1086,22977,1440],{"class":1146},[1086,22979,22980],{"class":1436}," signable",[1086,22982,861],{"class":1146},[1086,22984,22985],{"class":4109},"source_document\n",[1086,22987,22988,22991,22993,22996,22998,23001,23003,23006],{"class":1088,"line":3509},[1086,22989,22990],{"class":1436},"        pdf_bytes ",[1086,22992,1440],{"class":1146},[1086,22994,22995],{"class":1436}," source",[1086,22997,861],{"class":1146},[1086,22999,23000],{"class":4109},"pdf_file",[1086,23002,861],{"class":1146},[1086,23004,23005],{"class":1105},"read",[1086,23007,1387],{"class":1146},[1086,23009,23010,23013,23015,23017,23019,23021,23023,23025,23027,23030,23032,23034,23037,23039,23042,23044],{"class":1088,"line":3515},[1086,23011,23012],{"class":1436},"        filename ",[1086,23014,1440],{"class":1146},[1086,23016,22995],{"class":1436},[1086,23018,861],{"class":1146},[1086,23020,23000],{"class":4109},[1086,23022,861],{"class":1146},[1086,23024,4184],{"class":4109},[1086,23026,861],{"class":1146},[1086,23028,23029],{"class":1105},"split",[1086,23031,1398],{"class":1146},[1086,23033,1159],{"class":1146},[1086,23035,23036],{"class":1096},"/",[1086,23038,1159],{"class":1146},[1086,23040,23041],{"class":1146},")[-",[1086,23043,4434],{"class":1187},[1086,23045,1273],{"class":1146},[1086,23047,23048],{"class":1088,"line":3520},[1086,23049,3390],{"emptyLinePlaceholder":738},[1086,23051,23052],{"class":1088,"line":3526},[1086,23053,23054],{"class":1427},"        # 2. Upload to SignNow\n",[1086,23056,23057,23059,23061,23063,23065,23068],{"class":1088,"line":3531},[1086,23058,6800],{"class":1423},[1086,23060,6803],{"class":1146},[1086,23062,22980],{"class":1436},[1086,23064,861],{"class":1146},[1086,23066,23067],{"class":4109},"signnow_document_id",[1086,23069,1418],{"class":1146},[1086,23071,23072,23075,23077,23079,23081,23083,23085,23088,23090,23092],{"class":1088,"line":3537},[1086,23073,23074],{"class":1436},"            result ",[1086,23076,1440],{"class":1146},[1086,23078,19409],{"class":1436},[1086,23080,861],{"class":1146},[1086,23082,11690],{"class":1105},[1086,23084,1398],{"class":1146},[1086,23086,23087],{"class":1105},"pdf_bytes",[1086,23089,1227],{"class":1146},[1086,23091,20303],{"class":1105},[1086,23093,1455],{"class":1146},[1086,23095,23096,23098,23100,23103],{"class":1088,"line":3543},[1086,23097,6918],{"class":1423},[1086,23099,6803],{"class":1146},[1086,23101,23102],{"class":1436}," result",[1086,23104,1418],{"class":1146},[1086,23106,23107,23109,23112,23114,23116,23119,23121],{"class":1088,"line":3549},[1086,23108,6955],{"class":1423},[1086,23110,23111],{"class":1092}," RuntimeError",[1086,23113,1398],{"class":1146},[1086,23115,1159],{"class":1146},[1086,23117,23118],{"class":1096},"Failed to upload document to SignNow",[1086,23120,1159],{"class":1146},[1086,23122,1455],{"class":1146},[1086,23124,23125],{"class":1088,"line":3555},[1086,23126,3390],{"emptyLinePlaceholder":738},[1086,23128,23129,23132,23134,23136,23138,23140,23142,23144,23146,23148],{"class":1088,"line":3561},[1086,23130,23131],{"class":1436},"            signable",[1086,23133,861],{"class":1146},[1086,23135,23067],{"class":4109},[1086,23137,19552],{"class":1146},[1086,23139,23102],{"class":1436},[1086,23141,4340],{"class":1146},[1086,23143,1159],{"class":1146},[1086,23145,4156],{"class":1096},[1086,23147,1159],{"class":1146},[1086,23149,1273],{"class":1146},[1086,23151,23152,23154,23156,23158,23160,23162,23164,23166,23168],{"class":1088,"line":3567},[1086,23153,23131],{"class":1436},[1086,23155,861],{"class":1146},[1086,23157,11116],{"class":4109},[1086,23159,19552],{"class":1146},[1086,23161,21934],{"class":1436},[1086,23163,861],{"class":1146},[1086,23165,22373],{"class":4109},[1086,23167,861],{"class":1146},[1086,23169,23170],{"class":4109},"UPLOADED\n",[1086,23172,23173,23175,23177,23180,23182,23185,23187,23190],{"class":1088,"line":17749},[1086,23174,23131],{"class":1436},[1086,23176,861],{"class":1146},[1086,23178,23179],{"class":4109},"uploaded_at",[1086,23181,19552],{"class":1146},[1086,23183,23184],{"class":1436}," timezone",[1086,23186,861],{"class":1146},[1086,23188,23189],{"class":1105},"now",[1086,23191,1387],{"class":1146},[1086,23193,23194,23196,23198,23201,23203,23206,23209,23211,23213,23215,23217,23219,23221,23223,23225,23227,23229,23231],{"class":1088,"line":19843},[1086,23195,23131],{"class":1436},[1086,23197,861],{"class":1146},[1086,23199,23200],{"class":1105},"save",[1086,23202,1398],{"class":1146},[1086,23204,23205],{"class":1401},"update_fields",[1086,23207,23208],{"class":1146},"=[",[1086,23210,1159],{"class":1146},[1086,23212,23067],{"class":1096},[1086,23214,1159],{"class":1146},[1086,23216,1227],{"class":1146},[1086,23218,1195],{"class":1146},[1086,23220,11116],{"class":1096},[1086,23222,1159],{"class":1146},[1086,23224,1227],{"class":1146},[1086,23226,1195],{"class":1146},[1086,23228,23179],{"class":1096},[1086,23230,1159],{"class":1146},[1086,23232,23233],{"class":1146},"])\n",[1086,23235,23236],{"class":1088,"line":19848},[1086,23237,3390],{"emptyLinePlaceholder":738},[1086,23239,23240],{"class":1088,"line":19880},[1086,23241,23242],{"class":1427},"        # 3. Add signature fields\n",[1086,23244,23245,23248,23250,23253],{"class":1088,"line":19896},[1086,23246,23247],{"class":1436},"        SignNowService",[1086,23249,861],{"class":1146},[1086,23251,23252],{"class":1105},"add_signature_fields",[1086,23254,4094],{"class":1146},[1086,23256,23257,23259,23261,23263],{"class":1088,"line":19919},[1086,23258,23131],{"class":1105},[1086,23260,861],{"class":1146},[1086,23262,23067],{"class":4109},[1086,23264,1202],{"class":1146},[1086,23266,23267,23270,23273],{"class":1088,"line":19927},[1086,23268,23269],{"class":1146},"            [",[1086,23271,23272],{"class":1105},"TESTER_SIGNATURE_FIELD",[1086,23274,9297],{"class":1146},[1086,23276,23277],{"class":1088,"line":19948},[1086,23278,4133],{"class":1146},[1086,23280,23281],{"class":1088,"line":19968},[1086,23282,3390],{"emptyLinePlaceholder":738},[1086,23284,23285],{"class":1088,"line":19987},[1086,23286,23287],{"class":1427},"        # 4. Get role_id (assigned when fields were added)\n",[1086,23289,23290,23293,23295,23297,23299,23301,23303,23306,23308,23310],{"class":1088,"line":20007},[1086,23291,23292],{"class":1436},"        doc_data ",[1086,23294,1440],{"class":1146},[1086,23296,19409],{"class":1436},[1086,23298,861],{"class":1146},[1086,23300,11700],{"class":1105},[1086,23302,1398],{"class":1146},[1086,23304,23305],{"class":1105},"signable",[1086,23307,861],{"class":1146},[1086,23309,23067],{"class":4109},[1086,23311,1455],{"class":1146},[1086,23313,23314,23317,23319,23322,23324,23326,23328,23330,23333,23335,23337],{"class":1088,"line":20013},[1086,23315,23316],{"class":1436},"        roles ",[1086,23318,1440],{"class":1146},[1086,23320,23321],{"class":1436}," doc_data",[1086,23323,861],{"class":1146},[1086,23325,10812],{"class":1105},[1086,23327,1398],{"class":1146},[1086,23329,1159],{"class":1146},[1086,23331,23332],{"class":1096},"roles",[1086,23334,1159],{"class":1146},[1086,23336,1227],{"class":1146},[1086,23338,23339],{"class":1146}," [])\n",[1086,23341,23342,23345,23347,23350,23352,23355,23357,23360,23362,23365,23367,23370,23372,23374,23376,23378,23380,23382,23384,23386,23388,23390,23392],{"class":1088,"line":20021},[1086,23343,23344],{"class":1436},"        signer_role ",[1086,23346,1440],{"class":1146},[1086,23348,23349],{"class":1105}," next",[1086,23351,1398],{"class":1146},[1086,23353,23354],{"class":1105},"r ",[1086,23356,10799],{"class":1423},[1086,23358,23359],{"class":1105}," r ",[1086,23361,5931],{"class":1423},[1086,23363,23364],{"class":1105}," roles ",[1086,23366,11056],{"class":1423},[1086,23368,23369],{"class":1105}," r",[1086,23371,861],{"class":1146},[1086,23373,10812],{"class":1105},[1086,23375,1398],{"class":1146},[1086,23377,1159],{"class":1146},[1086,23379,4184],{"class":1096},[1086,23381,1159],{"class":1146},[1086,23383,1410],{"class":1146},[1086,23385,10847],{"class":1146},[1086,23387,1195],{"class":1146},[1086,23389,22754],{"class":1096},[1086,23391,1159],{"class":1146},[1086,23393,1455],{"class":1146},[1086,23395,23396],{"class":1088,"line":20051},[1086,23397,3390],{"emptyLinePlaceholder":738},[1086,23399,23400],{"class":1088,"line":20072},[1086,23401,23402],{"class":1427},"        # 5. Send role-based invite\n",[1086,23404,23405,23408,23410],{"class":1088,"line":20077},[1086,23406,23407],{"class":1436},"        signers ",[1086,23409,1440],{"class":1146},[1086,23411,23412],{"class":1146}," [{\n",[1086,23414,23415,23417,23419,23421,23423,23425,23427,23430],{"class":1088,"line":20083},[1086,23416,21603],{"class":1146},[1086,23418,17138],{"class":1096},[1086,23420,1159],{"class":1146},[1086,23422,1133],{"class":1146},[1086,23424,22980],{"class":1436},[1086,23426,861],{"class":1146},[1086,23428,23429],{"class":4109},"signer_email",[1086,23431,1202],{"class":1146},[1086,23433,23434,23436,23438,23440,23442,23444,23446,23448],{"class":1088,"line":20095},[1086,23435,21603],{"class":1146},[1086,23437,17362],{"class":1096},[1086,23439,1159],{"class":1146},[1086,23441,1133],{"class":1146},[1086,23443,1195],{"class":1146},[1086,23445,22754],{"class":1096},[1086,23447,1159],{"class":1146},[1086,23449,1202],{"class":1146},[1086,23451,23452,23454,23457,23459,23461,23464,23466,23468,23471,23473],{"class":1088,"line":20112},[1086,23453,21603],{"class":1146},[1086,23455,23456],{"class":1096},"role_id",[1086,23458,1159],{"class":1146},[1086,23460,1133],{"class":1146},[1086,23462,23463],{"class":1436}," signer_role",[1086,23465,4340],{"class":1146},[1086,23467,1159],{"class":1146},[1086,23469,23470],{"class":1096},"unique_id",[1086,23472,1159],{"class":1146},[1086,23474,9297],{"class":1146},[1086,23476,23477,23479,23482,23484,23486,23489],{"class":1088,"line":20117},[1086,23478,21603],{"class":1146},[1086,23480,23481],{"class":1096},"order",[1086,23483,1159],{"class":1146},[1086,23485,1133],{"class":1146},[1086,23487,23488],{"class":1187}," 1",[1086,23490,1202],{"class":1146},[1086,23492,23493],{"class":1088,"line":20139},[1086,23494,23495],{"class":1146},"        }]\n",[1086,23497,23498],{"class":1088,"line":20169},[1086,23499,3390],{"emptyLinePlaceholder":738},[1086,23501,23502,23505,23507,23509,23511,23513],{"class":1088,"line":20197},[1086,23503,23504],{"class":1436},"        invite_result ",[1086,23506,1440],{"class":1146},[1086,23508,19409],{"class":1436},[1086,23510,861],{"class":1146},[1086,23512,11740],{"class":1105},[1086,23514,4094],{"class":1146},[1086,23516,23517,23520,23522,23524,23526,23528],{"class":1088,"line":20227},[1086,23518,23519],{"class":1401},"            document_id",[1086,23521,1440],{"class":1146},[1086,23523,23305],{"class":1105},[1086,23525,861],{"class":1146},[1086,23527,23067],{"class":4109},[1086,23529,1202],{"class":1146},[1086,23531,23532,23535,23537,23540],{"class":1088,"line":20232},[1086,23533,23534],{"class":1401},"            signers",[1086,23536,1440],{"class":1146},[1086,23538,23539],{"class":1105},"signers",[1086,23541,1202],{"class":1146},[1086,23543,23545,23548,23550,23552,23554,23556],{"class":1088,"line":23544},66,[1086,23546,23547],{"class":1401},"            from_email",[1086,23549,1440],{"class":1146},[1086,23551,4104],{"class":1105},[1086,23553,861],{"class":1146},[1086,23555,12165],{"class":4109},[1086,23557,1202],{"class":1146},[1086,23559,23561],{"class":1088,"line":23560},67,[1086,23562,4133],{"class":1146},[1086,23564,23566],{"class":1088,"line":23565},68,[1086,23567,3390],{"emptyLinePlaceholder":738},[1086,23569,23571,23574,23576,23578,23580,23582,23584,23586,23588],{"class":1088,"line":23570},69,[1086,23572,23573],{"class":1436},"        signable",[1086,23575,861],{"class":1146},[1086,23577,11116],{"class":4109},[1086,23579,19552],{"class":1146},[1086,23581,21934],{"class":1436},[1086,23583,861],{"class":1146},[1086,23585,22373],{"class":4109},[1086,23587,861],{"class":1146},[1086,23589,23590],{"class":4109},"INVITE_SENT\n",[1086,23592,23594,23596,23598,23601,23603,23605,23607,23609],{"class":1088,"line":23593},70,[1086,23595,23573],{"class":1436},[1086,23597,861],{"class":1146},[1086,23599,23600],{"class":4109},"invite_sent_at",[1086,23602,19552],{"class":1146},[1086,23604,23184],{"class":1436},[1086,23606,861],{"class":1146},[1086,23608,23189],{"class":1105},[1086,23610,1387],{"class":1146},[1086,23612,23614,23616,23618,23620,23622,23624,23626,23628,23630,23632,23634,23636,23638,23640],{"class":1088,"line":23613},71,[1086,23615,23573],{"class":1436},[1086,23617,861],{"class":1146},[1086,23619,23200],{"class":1105},[1086,23621,1398],{"class":1146},[1086,23623,23205],{"class":1401},[1086,23625,23208],{"class":1146},[1086,23627,1159],{"class":1146},[1086,23629,11116],{"class":1096},[1086,23631,1159],{"class":1146},[1086,23633,1227],{"class":1146},[1086,23635,1195],{"class":1146},[1086,23637,23600],{"class":1096},[1086,23639,1159],{"class":1146},[1086,23641,23233],{"class":1146},[1086,23643,23645],{"class":1088,"line":23644},72,[1086,23646,3390],{"emptyLinePlaceholder":738},[1086,23648,23650],{"class":1088,"line":23649},73,[1086,23651,23652],{"class":1427},"        # 6. Register webhook for document completion\n",[1086,23654,23656,23659,23661,23663,23665],{"class":1088,"line":23655},74,[1086,23657,23658],{"class":1436},"        callback_url ",[1086,23660,1440],{"class":1146},[1086,23662,4270],{"class":1436},[1086,23664,861],{"class":1146},[1086,23666,23667],{"class":4109},"SIGNNOW_WEBHOOK_CALLBACK_URL\n",[1086,23669,23671,23673,23675],{"class":1088,"line":23670},75,[1086,23672,6800],{"class":1423},[1086,23674,21473],{"class":1436},[1086,23676,1418],{"class":1146},[1086,23678,23680,23683,23685,23687],{"class":1088,"line":23679},76,[1086,23681,23682],{"class":1436},"            SignNowService",[1086,23684,861],{"class":1146},[1086,23686,11790],{"class":1105},[1086,23688,4094],{"class":1146},[1086,23690,23692,23695,23697,23699,23702,23704],{"class":1088,"line":23691},77,[1086,23693,23694],{"class":1401},"                event",[1086,23696,1440],{"class":1146},[1086,23698,1159],{"class":1146},[1086,23700,23701],{"class":1096},"document.complete",[1086,23703,1159],{"class":1146},[1086,23705,1202],{"class":1146},[1086,23707,23709,23712,23714,23716,23718,23720],{"class":1088,"line":23708},78,[1086,23710,23711],{"class":1401},"                entity_id",[1086,23713,1440],{"class":1146},[1086,23715,23305],{"class":1105},[1086,23717,861],{"class":1146},[1086,23719,23067],{"class":4109},[1086,23721,1202],{"class":1146},[1086,23723,23725,23728,23730,23733],{"class":1088,"line":23724},79,[1086,23726,23727],{"class":1401},"                callback_url",[1086,23729,1440],{"class":1146},[1086,23731,23732],{"class":1105},"callback_url",[1086,23734,1202],{"class":1146},[1086,23736,23738],{"class":1088,"line":23737},80,[1086,23739,20080],{"class":1146},[1086,23741,23743],{"class":1088,"line":23742},81,[1086,23744,3390],{"emptyLinePlaceholder":738},[1086,23746,23748,23751,23754,23756,23759],{"class":1088,"line":23747},82,[1086,23749,23750],{"class":1423},"    except",[1086,23752,23753],{"class":1092}," Exception",[1086,23755,19873],{"class":1423},[1086,23757,23758],{"class":1436}," exc",[1086,23760,1418],{"class":1146},[1086,23762,23764,23766,23769,23771,23773,23775,23778,23781,23783,23785,23788],{"class":1088,"line":23763},83,[1086,23765,6800],{"class":1423},[1086,23767,23768],{"class":1436}," self",[1086,23770,861],{"class":1146},[1086,23772,4167],{"class":4109},[1086,23774,861],{"class":1146},[1086,23776,23777],{"class":4109},"retries",[1086,23779,23780],{"class":1146}," >=",[1086,23782,23768],{"class":1436},[1086,23784,861],{"class":1146},[1086,23786,23787],{"class":4109},"max_retries",[1086,23789,1418],{"class":1146},[1086,23791,23793,23795,23797,23799,23801,23803,23805,23807,23809],{"class":1088,"line":23792},84,[1086,23794,23131],{"class":1436},[1086,23796,861],{"class":1146},[1086,23798,11116],{"class":4109},[1086,23800,19552],{"class":1146},[1086,23802,21934],{"class":1436},[1086,23804,861],{"class":1146},[1086,23806,22373],{"class":4109},[1086,23808,861],{"class":1146},[1086,23810,23811],{"class":4109},"FAILED\n",[1086,23813,23815,23817,23819,23822,23824,23826,23828,23831],{"class":1088,"line":23814},85,[1086,23816,23131],{"class":1436},[1086,23818,861],{"class":1146},[1086,23820,23821],{"class":4109},"error_message",[1086,23823,19552],{"class":1146},[1086,23825,1407],{"class":1092},[1086,23827,1398],{"class":1146},[1086,23829,23830],{"class":1105},"exc",[1086,23832,1455],{"class":1146},[1086,23834,23836,23838,23840,23842,23844,23846,23848,23850,23852,23854,23856,23858,23860,23862],{"class":1088,"line":23835},86,[1086,23837,23131],{"class":1436},[1086,23839,861],{"class":1146},[1086,23841,23200],{"class":1105},[1086,23843,1398],{"class":1146},[1086,23845,23205],{"class":1401},[1086,23847,23208],{"class":1146},[1086,23849,1159],{"class":1146},[1086,23851,11116],{"class":1096},[1086,23853,1159],{"class":1146},[1086,23855,1227],{"class":1146},[1086,23857,1195],{"class":1146},[1086,23859,23821],{"class":1096},[1086,23861,1159],{"class":1146},[1086,23863,23233],{"class":1146},[1086,23865,23867],{"class":1088,"line":23866},87,[1086,23868,23869],{"class":1423},"            return\n",[1086,23871,23873,23876,23878,23881,23883,23885,23887,23889],{"class":1088,"line":23872},88,[1086,23874,23875],{"class":1436},"        self",[1086,23877,861],{"class":1146},[1086,23879,23880],{"class":1105},"retry",[1086,23882,1398],{"class":1146},[1086,23884,23830],{"class":1401},[1086,23886,1440],{"class":1146},[1086,23888,23830],{"class":1105},[1086,23890,1455],{"class":1146},[1572,23892,23893],{},[842,23894,5119,23895,23897,23898,5660,23900,5660,23902,5660,23904,23906,23907,23910],{},[895,23896,23272],{}," coordinates (",[895,23899,22647],{},[895,23901,22663],{},[895,23903,22679],{},[895,23905,22695],{},") define where the signature box appears on the PDF. You'll need to calibrate these values for your specific document layout. SignNow uses a coordinate system where ",[895,23908,23909],{},"(0,0)"," is the top-left corner of the page.",[863,23912,23914],{"id":23913},"step-5-handle-webhooks","Step 5: Handle webhooks",[842,23916,23917],{},"When the signer completes the document, SignNow sends a POST request to your webhook URL. We verify the payload signature and dispatch a download task:",[1013,23919,23922],{"className":1368,"code":23920,"filename":23921,"language":1250,"meta":728,"style":728},"import hashlib\nimport hmac\n\nfrom rest_framework.permissions import AllowAny\nfrom rest_framework.response import Response\nfrom rest_framework.views import APIView\n\n\nclass SignNowWebhookView(APIView):\n    \"\"\"Receives webhook callbacks from SignNow when documents are signed.\"\"\"\n\n    permission_classes = [AllowAny]\n    authentication_classes = []\n\n    def post(self, request):\n        # Verify webhook signature\n        webhook_secret = getattr(settings, \"SIGNNOW_WEBHOOK_SECRET\", \"\")\n        if webhook_secret:\n            received_signature = request.headers.get(\"X-SignNow-Signature\", \"\")\n            expected = hmac.new(\n                webhook_secret.encode(\"utf-8\"),\n                request.body,\n                hashlib.sha256,\n            ).hexdigest()\n\n            if not hmac.compare_digest(expected, received_signature):\n                return Response({\"status\": \"invalid_signature\"}, status=200)\n\n        # Parse event\n        event = request.data.get(\"event\", \"\")\n        meta = request.data.get(\"meta\", {})\n        document_id = meta.get(\"document_id\", \"\")\n\n        if event in (\"document.complete\", \"document.update\"):\n            signable = SignableDocument.objects.get(\n                signnow_document_id=document_id,\n                status__in=[\"uploaded\", \"invite_sent\"],\n            )\n            signable.status = SignableDocument.Status.SIGNED\n            signable.signed_at = timezone.now()\n            signable.save(update_fields=[\"status\", \"signed_at\"])\n\n            # Download the signed PDF asynchronously\n            download_signed_pdf.delay(signable.id)\n\n        # Always return 200 to acknowledge receipt\n        return Response({\"status\": \"ok\"}, status=200)\n","apps/signnow/api/views.py",[895,23923,23924,23931,23938,23942,23959,23974,23990,23994,23998,24011,24020,24024,24038,24047,24051,24067,24072,24099,24107,24139,24156,24176,24188,24200,24210,24214,24239,24276,24280,24285,24316,24347,24375,24379,24407,24426,24437,24460,24464,24485,24504,24534,24538,24543,24563,24567,24572],{"__ignoreMap":728},[1086,23925,23926,23928],{"class":1088,"line":1089},[1086,23927,6503],{"class":1423},[1086,23929,23930],{"class":1436}," hashlib\n",[1086,23932,23933,23935],{"class":1088,"line":729},[1086,23934,6503],{"class":1423},[1086,23936,23937],{"class":1436}," hmac\n",[1086,23939,23940],{"class":1088,"line":1112},[1086,23941,3390],{"emptyLinePlaceholder":738},[1086,23943,23944,23946,23949,23951,23954,23956],{"class":1088,"line":1181},[1086,23945,15570],{"class":1423},[1086,23947,23948],{"class":1436}," rest_framework",[1086,23950,861],{"class":1146},[1086,23952,23953],{"class":1436},"permissions ",[1086,23955,6503],{"class":1423},[1086,23957,23958],{"class":1436}," AllowAny\n",[1086,23960,23961,23963,23965,23967,23969,23971],{"class":1088,"line":1205},[1086,23962,15570],{"class":1423},[1086,23964,23948],{"class":1436},[1086,23966,861],{"class":1146},[1086,23968,7155],{"class":1436},[1086,23970,6503],{"class":1423},[1086,23972,23973],{"class":1436}," Response\n",[1086,23975,23976,23978,23980,23982,23985,23987],{"class":1088,"line":1276},[1086,23977,15570],{"class":1423},[1086,23979,23948],{"class":1436},[1086,23981,861],{"class":1146},[1086,23983,23984],{"class":1436},"views ",[1086,23986,6503],{"class":1423},[1086,23988,23989],{"class":1436}," APIView\n",[1086,23991,23992],{"class":1088,"line":1282},[1086,23993,3390],{"emptyLinePlaceholder":738},[1086,23995,23996],{"class":1088,"line":1288},[1086,23997,3390],{"emptyLinePlaceholder":738},[1086,23999,24000,24002,24005,24007,24009],{"class":1088,"line":2685},[1086,24001,4036],{"class":1155},[1086,24003,24004],{"class":1092}," SignNowWebhookView",[1086,24006,1398],{"class":1146},[1086,24008,4044],{"class":1092},[1086,24010,4047],{"class":1146},[1086,24012,24013,24015,24018],{"class":1088,"line":2700},[1086,24014,1424],{"class":1423},[1086,24016,24017],{"class":1427},"Receives webhook callbacks from SignNow when documents are signed.",[1086,24019,1431],{"class":1423},[1086,24021,24022],{"class":1088,"line":3398},[1086,24023,3390],{"emptyLinePlaceholder":738},[1086,24025,24026,24029,24031,24033,24036],{"class":1088,"line":1715},[1086,24027,24028],{"class":1436},"    permission_classes ",[1086,24030,1440],{"class":1146},[1086,24032,1217],{"class":1146},[1086,24034,24035],{"class":1436},"AllowAny",[1086,24037,1273],{"class":1146},[1086,24039,24040,24043,24045],{"class":1088,"line":3409},[1086,24041,24042],{"class":1436},"    authentication_classes ",[1086,24044,1440],{"class":1146},[1086,24046,5920],{"class":1146},[1086,24048,24049],{"class":1088,"line":3415},[1086,24050,3390],{"emptyLinePlaceholder":738},[1086,24052,24053,24055,24057,24059,24061,24063,24065],{"class":1088,"line":3421},[1086,24054,4065],{"class":1155},[1086,24056,4068],{"class":1105},[1086,24058,1398],{"class":1146},[1086,24060,4074],{"class":4073},[1086,24062,1227],{"class":1146},[1086,24064,4079],{"class":1401},[1086,24066,4047],{"class":1146},[1086,24068,24069],{"class":1088,"line":3427},[1086,24070,24071],{"class":1427},"        # Verify webhook signature\n",[1086,24073,24074,24077,24079,24081,24083,24085,24087,24089,24091,24093,24095,24097],{"class":1088,"line":3433},[1086,24075,24076],{"class":1436},"        webhook_secret ",[1086,24078,1440],{"class":1146},[1086,24080,6441],{"class":1105},[1086,24082,1398],{"class":1146},[1086,24084,4104],{"class":1105},[1086,24086,1227],{"class":1146},[1086,24088,1195],{"class":1146},[1086,24090,19061],{"class":1096},[1086,24092,1159],{"class":1146},[1086,24094,1227],{"class":1146},[1086,24096,19571],{"class":1146},[1086,24098,1455],{"class":1146},[1086,24100,24101,24103,24105],{"class":1088,"line":3439},[1086,24102,6800],{"class":1423},[1086,24104,21674],{"class":1436},[1086,24106,1418],{"class":1146},[1086,24108,24109,24112,24114,24116,24118,24120,24122,24124,24126,24128,24131,24133,24135,24137],{"class":1088,"line":3444},[1086,24110,24111],{"class":1436},"            received_signature ",[1086,24113,1440],{"class":1146},[1086,24115,4079],{"class":1436},[1086,24117,861],{"class":1146},[1086,24119,17277],{"class":4109},[1086,24121,861],{"class":1146},[1086,24123,10812],{"class":1105},[1086,24125,1398],{"class":1146},[1086,24127,1159],{"class":1146},[1086,24129,24130],{"class":1096},"X-SignNow-Signature",[1086,24132,1159],{"class":1146},[1086,24134,1227],{"class":1146},[1086,24136,19571],{"class":1146},[1086,24138,1455],{"class":1146},[1086,24140,24141,24144,24146,24149,24151,24154],{"class":1088,"line":3450},[1086,24142,24143],{"class":1436},"            expected ",[1086,24145,1440],{"class":1146},[1086,24147,24148],{"class":1436}," hmac",[1086,24150,861],{"class":1146},[1086,24152,24153],{"class":1105},"new",[1086,24155,4094],{"class":1146},[1086,24157,24158,24161,24163,24165,24167,24169,24172,24174],{"class":1088,"line":3456},[1086,24159,24160],{"class":1105},"                webhook_secret",[1086,24162,861],{"class":1146},[1086,24164,15797],{"class":1105},[1086,24166,1398],{"class":1146},[1086,24168,1159],{"class":1146},[1086,24170,24171],{"class":1096},"utf-8",[1086,24173,1159],{"class":1146},[1086,24175,20449],{"class":1146},[1086,24177,24178,24181,24183,24186],{"class":1088,"line":3462},[1086,24179,24180],{"class":1105},"                request",[1086,24182,861],{"class":1146},[1086,24184,24185],{"class":4109},"body",[1086,24187,1202],{"class":1146},[1086,24189,24190,24193,24195,24198],{"class":1088,"line":3467},[1086,24191,24192],{"class":1105},"                hashlib",[1086,24194,861],{"class":1146},[1086,24196,24197],{"class":4109},"sha256",[1086,24199,1202],{"class":1146},[1086,24201,24202,24205,24208],{"class":1088,"line":3473},[1086,24203,24204],{"class":1146},"            ).",[1086,24206,24207],{"class":1105},"hexdigest",[1086,24209,1387],{"class":1146},[1086,24211,24212],{"class":1088,"line":3479},[1086,24213,3390],{"emptyLinePlaceholder":738},[1086,24215,24216,24218,24220,24222,24224,24227,24229,24232,24234,24237],{"class":1088,"line":3485},[1086,24217,6918],{"class":1423},[1086,24219,6803],{"class":1146},[1086,24221,24148],{"class":1436},[1086,24223,861],{"class":1146},[1086,24225,24226],{"class":1105},"compare_digest",[1086,24228,1398],{"class":1146},[1086,24230,24231],{"class":1105},"expected",[1086,24233,1227],{"class":1146},[1086,24235,24236],{"class":1105}," received_signature",[1086,24238,4047],{"class":1146},[1086,24240,24241,24244,24246,24248,24250,24252,24254,24256,24258,24261,24263,24266,24269,24271,24274],{"class":1088,"line":3491},[1086,24242,24243],{"class":1423},"                return",[1086,24245,4242],{"class":1105},[1086,24247,4151],{"class":1146},[1086,24249,1159],{"class":1146},[1086,24251,11116],{"class":1096},[1086,24253,1159],{"class":1146},[1086,24255,1133],{"class":1146},[1086,24257,1195],{"class":1146},[1086,24259,24260],{"class":1096},"invalid_signature",[1086,24262,1159],{"class":1146},[1086,24264,24265],{"class":1146},"},",[1086,24267,24268],{"class":1401}," status",[1086,24270,1440],{"class":1146},[1086,24272,24273],{"class":1187},"200",[1086,24275,1455],{"class":1146},[1086,24277,24278],{"class":1088,"line":3497},[1086,24279,3390],{"emptyLinePlaceholder":738},[1086,24281,24282],{"class":1088,"line":3503},[1086,24283,24284],{"class":1427},"        # Parse event\n",[1086,24286,24287,24290,24292,24294,24296,24298,24300,24302,24304,24306,24308,24310,24312,24314],{"class":1088,"line":3509},[1086,24288,24289],{"class":1436},"        event ",[1086,24291,1440],{"class":1146},[1086,24293,4079],{"class":1436},[1086,24295,861],{"class":1146},[1086,24297,4337],{"class":4109},[1086,24299,861],{"class":1146},[1086,24301,10812],{"class":1105},[1086,24303,1398],{"class":1146},[1086,24305,1159],{"class":1146},[1086,24307,21543],{"class":1096},[1086,24309,1159],{"class":1146},[1086,24311,1227],{"class":1146},[1086,24313,19571],{"class":1146},[1086,24315,1455],{"class":1146},[1086,24317,24318,24321,24323,24325,24327,24329,24331,24333,24335,24337,24340,24342,24344],{"class":1088,"line":3515},[1086,24319,24320],{"class":1436},"        meta ",[1086,24322,1440],{"class":1146},[1086,24324,4079],{"class":1436},[1086,24326,861],{"class":1146},[1086,24328,4337],{"class":4109},[1086,24330,861],{"class":1146},[1086,24332,10812],{"class":1105},[1086,24334,1398],{"class":1146},[1086,24336,1159],{"class":1146},[1086,24338,24339],{"class":1096},"meta",[1086,24341,1159],{"class":1146},[1086,24343,1227],{"class":1146},[1086,24345,24346],{"class":1146}," {})\n",[1086,24348,24349,24352,24354,24357,24359,24361,24363,24365,24367,24369,24371,24373],{"class":1088,"line":3520},[1086,24350,24351],{"class":1436},"        document_id ",[1086,24353,1440],{"class":1146},[1086,24355,24356],{"class":1436}," meta",[1086,24358,861],{"class":1146},[1086,24360,10812],{"class":1105},[1086,24362,1398],{"class":1146},[1086,24364,1159],{"class":1146},[1086,24366,20701],{"class":1096},[1086,24368,1159],{"class":1146},[1086,24370,1227],{"class":1146},[1086,24372,19571],{"class":1146},[1086,24374,1455],{"class":1146},[1086,24376,24377],{"class":1088,"line":3526},[1086,24378,3390],{"emptyLinePlaceholder":738},[1086,24380,24381,24383,24386,24388,24390,24392,24394,24396,24398,24400,24403,24405],{"class":1088,"line":3531},[1086,24382,6800],{"class":1423},[1086,24384,24385],{"class":1436}," event ",[1086,24387,5931],{"class":1146},[1086,24389,5979],{"class":1146},[1086,24391,1159],{"class":1146},[1086,24393,23701],{"class":1096},[1086,24395,1159],{"class":1146},[1086,24397,1227],{"class":1146},[1086,24399,1195],{"class":1146},[1086,24401,24402],{"class":1096},"document.update",[1086,24404,1159],{"class":1146},[1086,24406,4047],{"class":1146},[1086,24408,24409,24412,24414,24416,24418,24420,24422,24424],{"class":1088,"line":3537},[1086,24410,24411],{"class":1436},"            signable ",[1086,24413,1440],{"class":1146},[1086,24415,21934],{"class":1436},[1086,24417,861],{"class":1146},[1086,24419,22921],{"class":4109},[1086,24421,861],{"class":1146},[1086,24423,10812],{"class":1105},[1086,24425,4094],{"class":1146},[1086,24427,24428,24431,24433,24435],{"class":1088,"line":3543},[1086,24429,24430],{"class":1401},"                signnow_document_id",[1086,24432,1440],{"class":1146},[1086,24434,20701],{"class":1105},[1086,24436,1202],{"class":1146},[1086,24438,24439,24442,24444,24446,24448,24450,24452,24454,24456,24458],{"class":1088,"line":3549},[1086,24440,24441],{"class":1401},"                status__in",[1086,24443,23208],{"class":1146},[1086,24445,1159],{"class":1146},[1086,24447,22012],{"class":1096},[1086,24449,1159],{"class":1146},[1086,24451,1227],{"class":1146},[1086,24453,1195],{"class":1146},[1086,24455,22035],{"class":1096},[1086,24457,1159],{"class":1146},[1086,24459,9297],{"class":1146},[1086,24461,24462],{"class":1088,"line":3555},[1086,24463,20080],{"class":1146},[1086,24465,24466,24468,24470,24472,24474,24476,24478,24480,24482],{"class":1088,"line":3561},[1086,24467,23131],{"class":1436},[1086,24469,861],{"class":1146},[1086,24471,11116],{"class":4109},[1086,24473,19552],{"class":1146},[1086,24475,21934],{"class":1436},[1086,24477,861],{"class":1146},[1086,24479,22373],{"class":4109},[1086,24481,861],{"class":1146},[1086,24483,24484],{"class":4109},"SIGNED\n",[1086,24486,24487,24489,24491,24494,24496,24498,24500,24502],{"class":1088,"line":3567},[1086,24488,23131],{"class":1436},[1086,24490,861],{"class":1146},[1086,24492,24493],{"class":4109},"signed_at",[1086,24495,19552],{"class":1146},[1086,24497,23184],{"class":1436},[1086,24499,861],{"class":1146},[1086,24501,23189],{"class":1105},[1086,24503,1387],{"class":1146},[1086,24505,24506,24508,24510,24512,24514,24516,24518,24520,24522,24524,24526,24528,24530,24532],{"class":1088,"line":17749},[1086,24507,23131],{"class":1436},[1086,24509,861],{"class":1146},[1086,24511,23200],{"class":1105},[1086,24513,1398],{"class":1146},[1086,24515,23205],{"class":1401},[1086,24517,23208],{"class":1146},[1086,24519,1159],{"class":1146},[1086,24521,11116],{"class":1096},[1086,24523,1159],{"class":1146},[1086,24525,1227],{"class":1146},[1086,24527,1195],{"class":1146},[1086,24529,24493],{"class":1096},[1086,24531,1159],{"class":1146},[1086,24533,23233],{"class":1146},[1086,24535,24536],{"class":1088,"line":19843},[1086,24537,3390],{"emptyLinePlaceholder":738},[1086,24539,24540],{"class":1088,"line":19848},[1086,24541,24542],{"class":1427},"            # Download the signed PDF asynchronously\n",[1086,24544,24545,24548,24550,24553,24555,24557,24559,24561],{"class":1088,"line":19880},[1086,24546,24547],{"class":1436},"            download_signed_pdf",[1086,24549,861],{"class":1146},[1086,24551,24552],{"class":1105},"delay",[1086,24554,1398],{"class":1146},[1086,24556,23305],{"class":1105},[1086,24558,861],{"class":1146},[1086,24560,4156],{"class":4109},[1086,24562,1455],{"class":1146},[1086,24564,24565],{"class":1088,"line":19896},[1086,24566,3390],{"emptyLinePlaceholder":738},[1086,24568,24569],{"class":1088,"line":19919},[1086,24570,24571],{"class":1427},"        # Always return 200 to acknowledge receipt\n",[1086,24573,24574,24576,24578,24580,24582,24584,24586,24588,24590,24593,24595,24597,24599,24601,24603],{"class":1088,"line":19927},[1086,24575,4239],{"class":1423},[1086,24577,4242],{"class":1105},[1086,24579,4151],{"class":1146},[1086,24581,1159],{"class":1146},[1086,24583,11116],{"class":1096},[1086,24585,1159],{"class":1146},[1086,24587,1133],{"class":1146},[1086,24589,1195],{"class":1146},[1086,24591,24592],{"class":1096},"ok",[1086,24594,1159],{"class":1146},[1086,24596,24265],{"class":1146},[1086,24598,24268],{"class":1401},[1086,24600,1440],{"class":1146},[1086,24602,24273],{"class":1187},[1086,24604,1455],{"class":1146},[1901,24606,24607],{},[842,24608,24609],{},"Always return HTTP 200 from your webhook endpoint, even when processing fails. SignNow will retry delivery on non-2xx responses, which can lead to duplicate processing if your error is in business logic rather than network issues.",[842,24611,24612],{},"Wire it up in your URL configuration:",[1013,24614,24617],{"className":1368,"code":24615,"filename":24616,"language":1250,"meta":728,"style":728},"from django.urls import path\nfrom signnow.api.views import SignNowWebhookView\n\nurlpatterns = [\n    path(\"webhooks/\", SignNowWebhookView.as_view(), name=\"signnow-webhook\"),\n]\n","apps/signnow/api/urls.py",[895,24618,24619,24635,24655,24659,24668,24708],{"__ignoreMap":728},[1086,24620,24621,24623,24625,24627,24630,24632],{"class":1088,"line":1089},[1086,24622,15570],{"class":1423},[1086,24624,19332],{"class":1436},[1086,24626,861],{"class":1146},[1086,24628,24629],{"class":1436},"urls ",[1086,24631,6503],{"class":1423},[1086,24633,24634],{"class":1436}," path\n",[1086,24636,24637,24639,24641,24643,24646,24648,24650,24652],{"class":1088,"line":729},[1086,24638,15570],{"class":1423},[1086,24640,11956],{"class":1436},[1086,24642,861],{"class":1146},[1086,24644,24645],{"class":1436},"api",[1086,24647,861],{"class":1146},[1086,24649,23984],{"class":1436},[1086,24651,6503],{"class":1423},[1086,24653,24654],{"class":1436}," SignNowWebhookView\n",[1086,24656,24657],{"class":1088,"line":1112},[1086,24658,3390],{"emptyLinePlaceholder":738},[1086,24660,24661,24664,24666],{"class":1088,"line":1181},[1086,24662,24663],{"class":1436},"urlpatterns ",[1086,24665,1440],{"class":1146},[1086,24667,6580],{"class":1146},[1086,24669,24670,24673,24675,24677,24680,24682,24684,24686,24688,24691,24694,24697,24699,24701,24704,24706],{"class":1088,"line":1205},[1086,24671,24672],{"class":1105},"    path",[1086,24674,1398],{"class":1146},[1086,24676,1159],{"class":1146},[1086,24678,24679],{"class":1096},"webhooks/",[1086,24681,1159],{"class":1146},[1086,24683,1227],{"class":1146},[1086,24685,24004],{"class":1105},[1086,24687,861],{"class":1146},[1086,24689,24690],{"class":1105},"as_view",[1086,24692,24693],{"class":1146},"(),",[1086,24695,24696],{"class":1401}," name",[1086,24698,1440],{"class":1146},[1086,24700,1159],{"class":1146},[1086,24702,24703],{"class":1096},"signnow-webhook",[1086,24705,1159],{"class":1146},[1086,24707,20449],{"class":1146},[1086,24709,24710],{"class":1088,"line":1276},[1086,24711,1273],{"class":1146},[842,24713,24714],{},"And include it in your main API URLs:",[1013,24716,24719],{"className":1368,"code":24717,"filename":24718,"language":1250,"meta":728,"style":728},"urlpatterns = [\n    # ... other routes\n    path(\"v1/signnow/\", include(\"signnow.api.urls\")),\n]\n","apps/api/urls.py",[895,24720,24721,24729,24734,24764],{"__ignoreMap":728},[1086,24722,24723,24725,24727],{"class":1088,"line":1089},[1086,24724,24663],{"class":1436},[1086,24726,1440],{"class":1146},[1086,24728,6580],{"class":1146},[1086,24730,24731],{"class":1088,"line":729},[1086,24732,24733],{"class":1427},"    # ... other routes\n",[1086,24735,24736,24738,24740,24742,24745,24747,24749,24752,24754,24756,24759,24761],{"class":1088,"line":1112},[1086,24737,24672],{"class":1105},[1086,24739,1398],{"class":1146},[1086,24741,1159],{"class":1146},[1086,24743,24744],{"class":1096},"v1/signnow/",[1086,24746,1159],{"class":1146},[1086,24748,1227],{"class":1146},[1086,24750,24751],{"class":1105}," include",[1086,24753,1398],{"class":1146},[1086,24755,1159],{"class":1146},[1086,24757,24758],{"class":1096},"signnow.api.urls",[1086,24760,1159],{"class":1146},[1086,24762,24763],{"class":1146},")),\n",[1086,24765,24766],{"class":1088,"line":1181},[1086,24767,1273],{"class":1146},[863,24769,24771],{"id":24770},"step-6-download-the-signed-pdf","Step 6: Download the signed PDF",[842,24773,24774],{},"The second Celery task fetches the completed, signed document from SignNow and saves it locally:",[1013,24776,24778],{"className":1368,"code":24777,"filename":22608,"language":1250,"meta":728,"style":728},"@shared_task(bind=True, max_retries=5, default_retry_delay=120)\ndef download_signed_pdf(self, signable_document_id: int):\n    \"\"\"Download the signed PDF from SignNow and save it locally.\"\"\"\n    from signnow.models import SignableDocument\n    from signnow.services.signnow_service import SignNowService\n\n    signable = SignableDocument.objects.get(id=signable_document_id)\n\n    # Skip if already downloaded (idempotent)\n    if signable.status == SignableDocument.Status.DOWNLOADED:\n        return\n\n    pdf_bytes = SignNowService.download_signed_document(signable.signnow_document_id)\n    if not pdf_bytes:\n        raise RuntimeError(\"Failed to download signed document\")\n\n    signed_filename = f\"signed-{signable.original_pdf_name.split('/')[-1]}\"\n    signable.signed_pdf.save(signed_filename, ContentFile(pdf_bytes), save=False)\n    signable.status = SignableDocument.Status.DOWNLOADED\n    signable.downloaded_at = timezone.now()\n    signable.save(update_fields=[\"signed_pdf\", \"status\", \"downloaded_at\"])\n",[895,24779,24780,24809,24830,24839,24853,24871,24875,24902,24906,24911,24936,24941,24945,24968,24978,24996,25000,25043,25078,25099,25118],{"__ignoreMap":728},[1086,24781,24782,24784,24786,24788,24790,24792,24794,24796,24798,24800,24802,24804,24807],{"class":1088,"line":1089},[1086,24783,1376],{"class":1146},[1086,24785,22809],{"class":1105},[1086,24787,1398],{"class":1146},[1086,24789,22814],{"class":1401},[1086,24791,22246],{"class":1146},[1086,24793,22819],{"class":1401},[1086,24795,1440],{"class":1146},[1086,24797,8131],{"class":1187},[1086,24799,1227],{"class":1146},[1086,24801,22828],{"class":1401},[1086,24803,1440],{"class":1146},[1086,24805,24806],{"class":1187},"120",[1086,24808,1455],{"class":1146},[1086,24810,24811,24813,24816,24818,24820,24822,24824,24826,24828],{"class":1088,"line":729},[1086,24812,1392],{"class":1155},[1086,24814,24815],{"class":1105}," download_signed_pdf",[1086,24817,1398],{"class":1146},[1086,24819,4074],{"class":4073},[1086,24821,1227],{"class":1146},[1086,24823,22851],{"class":1401},[1086,24825,1133],{"class":1146},[1086,24827,22856],{"class":1092},[1086,24829,4047],{"class":1146},[1086,24831,24832,24834,24837],{"class":1088,"line":1112},[1086,24833,1424],{"class":1423},[1086,24835,24836],{"class":1427},"Download the signed PDF from SignNow and save it locally.",[1086,24838,1431],{"class":1423},[1086,24840,24841,24843,24845,24847,24849,24851],{"class":1088,"line":1181},[1086,24842,6524],{"class":1423},[1086,24844,11956],{"class":1436},[1086,24846,861],{"class":1146},[1086,24848,21898],{"class":1436},[1086,24850,6503],{"class":1423},[1086,24852,22882],{"class":1436},[1086,24854,24855,24857,24859,24861,24863,24865,24867,24869],{"class":1088,"line":1205},[1086,24856,6524],{"class":1423},[1086,24858,11956],{"class":1436},[1086,24860,861],{"class":1146},[1086,24862,22893],{"class":1436},[1086,24864,861],{"class":1146},[1086,24866,22898],{"class":1436},[1086,24868,6503],{"class":1423},[1086,24870,22903],{"class":1436},[1086,24872,24873],{"class":1088,"line":1276},[1086,24874,3390],{"emptyLinePlaceholder":738},[1086,24876,24877,24879,24881,24883,24885,24887,24889,24891,24893,24895,24897,24900],{"class":1088,"line":1282},[1086,24878,22912],{"class":1436},[1086,24880,1440],{"class":1146},[1086,24882,21934],{"class":1436},[1086,24884,861],{"class":1146},[1086,24886,22921],{"class":4109},[1086,24888,861],{"class":1146},[1086,24890,10812],{"class":1105},[1086,24892,1398],{"class":1146},[1086,24894,4156],{"class":1401},[1086,24896,1440],{"class":1146},[1086,24898,24899],{"class":1105},"signable_document_id",[1086,24901,1455],{"class":1146},[1086,24903,24904],{"class":1088,"line":1288},[1086,24905,3390],{"emptyLinePlaceholder":738},[1086,24907,24908],{"class":1088,"line":2685},[1086,24909,24910],{"class":1427},"    # Skip if already downloaded (idempotent)\n",[1086,24912,24913,24915,24917,24919,24921,24923,24925,24927,24929,24931,24934],{"class":1088,"line":2700},[1086,24914,6474],{"class":1423},[1086,24916,22980],{"class":1436},[1086,24918,861],{"class":1146},[1086,24920,11116],{"class":4109},[1086,24922,10847],{"class":1146},[1086,24924,21934],{"class":1436},[1086,24926,861],{"class":1146},[1086,24928,22373],{"class":4109},[1086,24930,861],{"class":1146},[1086,24932,24933],{"class":4109},"DOWNLOADED",[1086,24935,1418],{"class":1146},[1086,24937,24938],{"class":1088,"line":3398},[1086,24939,24940],{"class":1423},"        return\n",[1086,24942,24943],{"class":1088,"line":1715},[1086,24944,3390],{"emptyLinePlaceholder":738},[1086,24946,24947,24950,24952,24954,24956,24958,24960,24962,24964,24966],{"class":1088,"line":3409},[1086,24948,24949],{"class":1436},"    pdf_bytes ",[1086,24951,1440],{"class":1146},[1086,24953,19409],{"class":1436},[1086,24955,861],{"class":1146},[1086,24957,11720],{"class":1105},[1086,24959,1398],{"class":1146},[1086,24961,23305],{"class":1105},[1086,24963,861],{"class":1146},[1086,24965,23067],{"class":4109},[1086,24967,1455],{"class":1146},[1086,24969,24970,24972,24974,24976],{"class":1088,"line":3415},[1086,24971,6474],{"class":1423},[1086,24973,6803],{"class":1146},[1086,24975,20293],{"class":1436},[1086,24977,1418],{"class":1146},[1086,24979,24980,24983,24985,24987,24989,24992,24994],{"class":1088,"line":3421},[1086,24981,24982],{"class":1423},"        raise",[1086,24984,23111],{"class":1092},[1086,24986,1398],{"class":1146},[1086,24988,1159],{"class":1146},[1086,24990,24991],{"class":1096},"Failed to download signed document",[1086,24993,1159],{"class":1146},[1086,24995,1455],{"class":1146},[1086,24997,24998],{"class":1088,"line":3427},[1086,24999,3390],{"emptyLinePlaceholder":738},[1086,25001,25002,25005,25007,25009,25012,25014,25016,25018,25021,25023,25025,25027,25029,25031,25033,25035,25037,25039,25041],{"class":1088,"line":3433},[1086,25003,25004],{"class":1436},"    signed_filename ",[1086,25006,1440],{"class":1146},[1086,25008,4403],{"class":1155},[1086,25010,25011],{"class":1096},"\"signed-",[1086,25013,4409],{"class":1187},[1086,25015,23305],{"class":1436},[1086,25017,861],{"class":1146},[1086,25019,25020],{"class":4109},"original_pdf_name",[1086,25022,861],{"class":1146},[1086,25024,23029],{"class":1105},[1086,25026,1398],{"class":1146},[1086,25028,10742],{"class":1146},[1086,25030,23036],{"class":1096},[1086,25032,10742],{"class":1146},[1086,25034,23041],{"class":1146},[1086,25036,4434],{"class":1187},[1086,25038,4420],{"class":1146},[1086,25040,4423],{"class":1187},[1086,25042,4441],{"class":1096},[1086,25044,25045,25048,25050,25053,25055,25057,25059,25062,25064,25067,25069,25071,25073,25076],{"class":1088,"line":3439},[1086,25046,25047],{"class":1436},"    signable",[1086,25049,861],{"class":1146},[1086,25051,25052],{"class":4109},"signed_pdf",[1086,25054,861],{"class":1146},[1086,25056,23200],{"class":1105},[1086,25058,1398],{"class":1146},[1086,25060,25061],{"class":1105},"signed_filename",[1086,25063,1227],{"class":1146},[1086,25065,25066],{"class":1105}," ContentFile",[1086,25068,1398],{"class":1146},[1086,25070,23087],{"class":1105},[1086,25072,4179],{"class":1146},[1086,25074,25075],{"class":1401}," save",[1086,25077,1491],{"class":1146},[1086,25079,25080,25082,25084,25086,25088,25090,25092,25094,25096],{"class":1088,"line":3444},[1086,25081,25047],{"class":1436},[1086,25083,861],{"class":1146},[1086,25085,11116],{"class":4109},[1086,25087,19552],{"class":1146},[1086,25089,21934],{"class":1436},[1086,25091,861],{"class":1146},[1086,25093,22373],{"class":4109},[1086,25095,861],{"class":1146},[1086,25097,25098],{"class":4109},"DOWNLOADED\n",[1086,25100,25101,25103,25105,25108,25110,25112,25114,25116],{"class":1088,"line":3450},[1086,25102,25047],{"class":1436},[1086,25104,861],{"class":1146},[1086,25106,25107],{"class":4109},"downloaded_at",[1086,25109,19552],{"class":1146},[1086,25111,23184],{"class":1436},[1086,25113,861],{"class":1146},[1086,25115,23189],{"class":1105},[1086,25117,1387],{"class":1146},[1086,25119,25120,25122,25124,25126,25128,25130,25132,25134,25136,25138,25140,25142,25144,25146,25148,25150,25152,25154],{"class":1088,"line":3456},[1086,25121,25047],{"class":1436},[1086,25123,861],{"class":1146},[1086,25125,23200],{"class":1105},[1086,25127,1398],{"class":1146},[1086,25129,23205],{"class":1401},[1086,25131,23208],{"class":1146},[1086,25133,1159],{"class":1146},[1086,25135,25052],{"class":1096},[1086,25137,1159],{"class":1146},[1086,25139,1227],{"class":1146},[1086,25141,1195],{"class":1146},[1086,25143,11116],{"class":1096},[1086,25145,1159],{"class":1146},[1086,25147,1227],{"class":1146},[1086,25149,1195],{"class":1146},[1086,25151,25107],{"class":1096},[1086,25153,1159],{"class":1146},[1086,25155,23233],{"class":1146},[863,25157,25159],{"id":25158},"step-7-admin-integration","Step 7: Admin integration",[842,25161,25162],{},"Finally, we added a read-only admin panel with color-coded status badges so our team can monitor signing progress at a glance:",[1013,25164,25167],{"className":1368,"code":25165,"filename":25166,"language":1250,"meta":728,"style":728},"from django.contrib import admin\nfrom django.utils.html import format_html\n\n\n@admin.register(SignableDocument)\nclass SignableDocumentAdmin(admin.ModelAdmin):\n    list_display = [\"signer_name\", \"signer_email\", \"status_badge\", \"created_at\", \"signed_at\"]\n    list_filter = [\"status\", \"created_at\"]\n    search_fields = [\"signer_email\", \"signer_name\", \"signnow_document_id\"]\n\n    @admin.display(description=\"Status\")\n    def status_badge(self, obj):\n        colors = {\n            \"pending\": \"#6b7280\",\n            \"uploaded\": \"#3b82f6\",\n            \"invite_sent\": \"#f59e0b\",\n            \"signed\": \"#22c55e\",\n            \"downloaded\": \"#059669\",\n            \"failed\": \"#ef4444\",\n        }\n        color = colors.get(obj.status, \"#6b7280\")\n        return format_html(\n            '\u003Cspan style=\"background-color: {}; color: white; padding: 2px 8px; '\n            'border-radius: 4px; font-size: 12px;\">{}\u003C/span>',\n            color,\n            obj.get_status_display(),\n        )\n","apps/signnow/admin.py",[895,25168,25169,25185,25206,25210,25214,25232,25250,25302,25327,25360,25364,25389,25407,25416,25435,25454,25472,25491,25510,25529,25534,25567,25576,25593,25609,25616,25629],{"__ignoreMap":728},[1086,25170,25171,25173,25175,25177,25180,25182],{"class":1088,"line":1089},[1086,25172,15570],{"class":1423},[1086,25174,19332],{"class":1436},[1086,25176,861],{"class":1146},[1086,25178,25179],{"class":1436},"contrib ",[1086,25181,6503],{"class":1423},[1086,25183,25184],{"class":1436}," admin\n",[1086,25186,25187,25189,25191,25193,25196,25198,25201,25203],{"class":1088,"line":729},[1086,25188,15570],{"class":1423},[1086,25190,19332],{"class":1436},[1086,25192,861],{"class":1146},[1086,25194,25195],{"class":1436},"utils",[1086,25197,861],{"class":1146},[1086,25199,25200],{"class":1436},"html ",[1086,25202,6503],{"class":1423},[1086,25204,25205],{"class":1436}," format_html\n",[1086,25207,25208],{"class":1088,"line":1112},[1086,25209,3390],{"emptyLinePlaceholder":738},[1086,25211,25212],{"class":1088,"line":1181},[1086,25213,3390],{"emptyLinePlaceholder":738},[1086,25215,25216,25218,25221,25223,25226,25228,25230],{"class":1088,"line":1205},[1086,25217,1376],{"class":1146},[1086,25219,25220],{"class":1105},"admin",[1086,25222,861],{"class":1146},[1086,25224,25225],{"class":1105},"register",[1086,25227,1398],{"class":1146},[1086,25229,18952],{"class":1105},[1086,25231,1455],{"class":1146},[1086,25233,25234,25236,25239,25241,25243,25245,25248],{"class":1088,"line":1276},[1086,25235,4036],{"class":1155},[1086,25237,25238],{"class":1092}," SignableDocumentAdmin",[1086,25240,1398],{"class":1146},[1086,25242,25220],{"class":1092},[1086,25244,861],{"class":1146},[1086,25246,25247],{"class":1092},"ModelAdmin",[1086,25249,4047],{"class":1146},[1086,25251,25252,25255,25257,25259,25261,25264,25266,25268,25270,25272,25274,25276,25278,25281,25283,25285,25287,25290,25292,25294,25296,25298,25300],{"class":1088,"line":1282},[1086,25253,25254],{"class":1436},"    list_display ",[1086,25256,1440],{"class":1146},[1086,25258,1217],{"class":1146},[1086,25260,1159],{"class":1146},[1086,25262,25263],{"class":1096},"signer_name",[1086,25265,1159],{"class":1146},[1086,25267,1227],{"class":1146},[1086,25269,1195],{"class":1146},[1086,25271,23429],{"class":1096},[1086,25273,1159],{"class":1146},[1086,25275,1227],{"class":1146},[1086,25277,1195],{"class":1146},[1086,25279,25280],{"class":1096},"status_badge",[1086,25282,1159],{"class":1146},[1086,25284,1227],{"class":1146},[1086,25286,1195],{"class":1146},[1086,25288,25289],{"class":1096},"created_at",[1086,25291,1159],{"class":1146},[1086,25293,1227],{"class":1146},[1086,25295,1195],{"class":1146},[1086,25297,24493],{"class":1096},[1086,25299,1159],{"class":1146},[1086,25301,1273],{"class":1146},[1086,25303,25304,25307,25309,25311,25313,25315,25317,25319,25321,25323,25325],{"class":1088,"line":1288},[1086,25305,25306],{"class":1436},"    list_filter ",[1086,25308,1440],{"class":1146},[1086,25310,1217],{"class":1146},[1086,25312,1159],{"class":1146},[1086,25314,11116],{"class":1096},[1086,25316,1159],{"class":1146},[1086,25318,1227],{"class":1146},[1086,25320,1195],{"class":1146},[1086,25322,25289],{"class":1096},[1086,25324,1159],{"class":1146},[1086,25326,1273],{"class":1146},[1086,25328,25329,25332,25334,25336,25338,25340,25342,25344,25346,25348,25350,25352,25354,25356,25358],{"class":1088,"line":2685},[1086,25330,25331],{"class":1436},"    search_fields ",[1086,25333,1440],{"class":1146},[1086,25335,1217],{"class":1146},[1086,25337,1159],{"class":1146},[1086,25339,23429],{"class":1096},[1086,25341,1159],{"class":1146},[1086,25343,1227],{"class":1146},[1086,25345,1195],{"class":1146},[1086,25347,25263],{"class":1096},[1086,25349,1159],{"class":1146},[1086,25351,1227],{"class":1146},[1086,25353,1195],{"class":1146},[1086,25355,23067],{"class":1096},[1086,25357,1159],{"class":1146},[1086,25359,1273],{"class":1146},[1086,25361,25362],{"class":1088,"line":2700},[1086,25363,3390],{"emptyLinePlaceholder":738},[1086,25365,25366,25368,25370,25372,25375,25377,25379,25381,25383,25385,25387],{"class":1088,"line":3398},[1086,25367,6734],{"class":1146},[1086,25369,25220],{"class":1105},[1086,25371,861],{"class":1146},[1086,25373,25374],{"class":1105},"display",[1086,25376,1398],{"class":1146},[1086,25378,15465],{"class":1401},[1086,25380,1440],{"class":1146},[1086,25382,1159],{"class":1146},[1086,25384,22373],{"class":1096},[1086,25386,1159],{"class":1146},[1086,25388,1455],{"class":1146},[1086,25390,25391,25393,25396,25398,25400,25402,25405],{"class":1088,"line":1715},[1086,25392,4065],{"class":1155},[1086,25394,25395],{"class":1105}," status_badge",[1086,25397,1398],{"class":1146},[1086,25399,4074],{"class":4073},[1086,25401,1227],{"class":1146},[1086,25403,25404],{"class":1401}," obj",[1086,25406,4047],{"class":1146},[1086,25408,25409,25412,25414],{"class":1088,"line":3409},[1086,25410,25411],{"class":1436},"        colors ",[1086,25413,1440],{"class":1146},[1086,25415,1164],{"class":1146},[1086,25417,25418,25420,25422,25424,25426,25428,25431,25433],{"class":1088,"line":3415},[1086,25419,21603],{"class":1146},[1086,25421,21989],{"class":1096},[1086,25423,1159],{"class":1146},[1086,25425,1133],{"class":1146},[1086,25427,1195],{"class":1146},[1086,25429,25430],{"class":1096},"#6b7280",[1086,25432,1159],{"class":1146},[1086,25434,1202],{"class":1146},[1086,25436,25437,25439,25441,25443,25445,25447,25450,25452],{"class":1088,"line":3421},[1086,25438,21603],{"class":1146},[1086,25440,22012],{"class":1096},[1086,25442,1159],{"class":1146},[1086,25444,1133],{"class":1146},[1086,25446,1195],{"class":1146},[1086,25448,25449],{"class":1096},"#3b82f6",[1086,25451,1159],{"class":1146},[1086,25453,1202],{"class":1146},[1086,25455,25456,25458,25460,25462,25464,25466,25468,25470],{"class":1088,"line":3427},[1086,25457,21603],{"class":1146},[1086,25459,22035],{"class":1096},[1086,25461,1159],{"class":1146},[1086,25463,1133],{"class":1146},[1086,25465,1195],{"class":1146},[1086,25467,3237],{"class":1096},[1086,25469,1159],{"class":1146},[1086,25471,1202],{"class":1146},[1086,25473,25474,25476,25478,25480,25482,25484,25487,25489],{"class":1088,"line":3433},[1086,25475,21603],{"class":1146},[1086,25477,22058],{"class":1096},[1086,25479,1159],{"class":1146},[1086,25481,1133],{"class":1146},[1086,25483,1195],{"class":1146},[1086,25485,25486],{"class":1096},"#22c55e",[1086,25488,1159],{"class":1146},[1086,25490,1202],{"class":1146},[1086,25492,25493,25495,25497,25499,25501,25503,25506,25508],{"class":1088,"line":3439},[1086,25494,21603],{"class":1146},[1086,25496,22081],{"class":1096},[1086,25498,1159],{"class":1146},[1086,25500,1133],{"class":1146},[1086,25502,1195],{"class":1146},[1086,25504,25505],{"class":1096},"#059669",[1086,25507,1159],{"class":1146},[1086,25509,1202],{"class":1146},[1086,25511,25512,25514,25516,25518,25520,25522,25525,25527],{"class":1088,"line":3444},[1086,25513,21603],{"class":1146},[1086,25515,22104],{"class":1096},[1086,25517,1159],{"class":1146},[1086,25519,1133],{"class":1146},[1086,25521,1195],{"class":1146},[1086,25523,25524],{"class":1096},"#ef4444",[1086,25526,1159],{"class":1146},[1086,25528,1202],{"class":1146},[1086,25530,25531],{"class":1088,"line":3450},[1086,25532,25533],{"class":1146},"        }\n",[1086,25535,25536,25539,25541,25544,25546,25548,25550,25553,25555,25557,25559,25561,25563,25565],{"class":1088,"line":3456},[1086,25537,25538],{"class":1436},"        color ",[1086,25540,1440],{"class":1146},[1086,25542,25543],{"class":1436}," colors",[1086,25545,861],{"class":1146},[1086,25547,10812],{"class":1105},[1086,25549,1398],{"class":1146},[1086,25551,25552],{"class":1105},"obj",[1086,25554,861],{"class":1146},[1086,25556,11116],{"class":4109},[1086,25558,1227],{"class":1146},[1086,25560,1195],{"class":1146},[1086,25562,25430],{"class":1096},[1086,25564,1159],{"class":1146},[1086,25566,1455],{"class":1146},[1086,25568,25569,25571,25574],{"class":1088,"line":3462},[1086,25570,4239],{"class":1423},[1086,25572,25573],{"class":1105}," format_html",[1086,25575,4094],{"class":1146},[1086,25577,25578,25581,25584,25587,25590],{"class":1088,"line":3467},[1086,25579,25580],{"class":1146},"            '",[1086,25582,25583],{"class":1096},"\u003Cspan style=\"background-color: ",[1086,25585,25586],{"class":1187},"{}",[1086,25588,25589],{"class":1096},"; color: white; padding: 2px 8px; ",[1086,25591,25592],{"class":1146},"'\n",[1086,25594,25595,25597,25600,25602,25605,25607],{"class":1088,"line":3473},[1086,25596,25580],{"class":1146},[1086,25598,25599],{"class":1096},"border-radius: 4px; font-size: 12px;\">",[1086,25601,25586],{"class":1187},[1086,25603,25604],{"class":1096},"\u003C/span>",[1086,25606,10742],{"class":1146},[1086,25608,1202],{"class":1146},[1086,25610,25611,25614],{"class":1088,"line":3479},[1086,25612,25613],{"class":1105},"            color",[1086,25615,1202],{"class":1146},[1086,25617,25618,25621,25623,25626],{"class":1088,"line":3485},[1086,25619,25620],{"class":1105},"            obj",[1086,25622,861],{"class":1146},[1086,25624,25625],{"class":1105},"get_status_display",[1086,25627,25628],{"class":1146},"(),\n",[1086,25630,25631],{"class":1088,"line":3491},[1086,25632,4133],{"class":1146},[863,25634,25636],{"id":25635},"lessons-learned","Lessons learned",[842,25638,25639],{},"After running this in production, here are the things we wish we'd known earlier:",[1074,25641,25643],{"id":25642},"_1-role-based-invites-require-a-two-step-process","1. Role-based invites require a two-step process",[842,25645,25646,25647,25650,25651,25653],{},"You can't send a role-based invite immediately after adding fields. You need to ",[996,25648,25649],{},"re-fetch the document"," to get the ",[895,25652,23456],{}," that SignNow assigns when processing the fields. This caught us off guard because freeform invites don't have this requirement.",[1074,25655,25657],{"id":25656},"_2-token-caching-is-essential","2. Token caching is essential",[842,25659,25660],{},"SignNow's OAuth2 tokens last about an hour. Without caching, every API call would require a round-trip to the token endpoint first. We use Django's cache framework (backed by Redis) to store tokens with a 5-minute safety buffer before expiry.",[1074,25662,25664],{"id":25663},"_3-webhook-signatures-use-hmac-sha256","3. Webhook signatures use HMAC-SHA256",[842,25666,25667,25668,25670,25671,25674],{},"Always verify webhook payloads in production. SignNow sends a ",[895,25669,24130],{}," header containing an HMAC-SHA256 digest of the request body. Use ",[895,25672,25673],{},"hmac.compare_digest()"," for timing-safe comparison.",[1074,25676,25678],{"id":25677},"_4-custom-invite-messages-require-a-paid-plan","4. Custom invite messages require a paid plan",[842,25680,25681],{},"If you're using the free sandbox, you can send invites, but custom subjects and messages are a paid feature. The default invite email from SignNow is generic but functional for testing.",[1074,25683,25685],{"id":25684},"_5-signature-field-coordinates-need-calibration","5. Signature field coordinates need calibration",[842,25687,5119,25688,5660,25690,5660,25692,7378,25694,25696],{},[895,25689,22647],{},[895,25691,22663],{},[895,25693,22679],{},[895,25695,22695],{}," values for signature fields depend entirely on your PDF layout. We recommend uploading a test document to SignNow's web UI, manually placing a signature field, and then inspecting the document's JSON to capture the exact coordinates.",[863,25698,25699],{"id":8196},"Summary",[842,25701,25702],{},"Here's the complete API flow at a glance:",[871,25704,25705,25719],{},[874,25706,25707],{},[877,25708,25709,25711,25714,25717],{},[880,25710,10440],{},[880,25712,25713],{},"API Endpoint",[880,25715,25716],{},"Method",[880,25718,3021],{},[887,25720,25721,25735,25749,25764,25778,25792,25806],{},[877,25722,25723,25725,25730,25732],{},[892,25724,4434],{},[892,25726,25727],{},[895,25728,25729],{},"/oauth2/token",[892,25731,4685],{},[892,25733,25734],{},"Get access token",[877,25736,25737,25739,25744,25746],{},[892,25738,1483],{},[892,25740,25741],{},[895,25742,25743],{},"/document",[892,25745,4685],{},[892,25747,25748],{},"Upload PDF",[877,25750,25751,25753,25758,25761],{},[892,25752,7948],{},[892,25754,25755],{},[895,25756,25757],{},"/document/{id}",[892,25759,25760],{},"PUT",[892,25762,25763],{},"Add signature fields",[877,25765,25766,25768,25772,25775],{},[892,25767,7989],{},[892,25769,25770],{},[895,25771,25757],{},[892,25773,25774],{},"GET",[892,25776,25777],{},"Retrieve role IDs",[877,25779,25780,25782,25787,25789],{},[892,25781,8131],{},[892,25783,25784],{},[895,25785,25786],{},"/document/{id}/invite",[892,25788,4685],{},[892,25790,25791],{},"Send signing invite",[877,25793,25794,25796,25801,25803],{},[892,25795,7970],{},[892,25797,25798],{},[895,25799,25800],{},"/v2/events",[892,25802,4685],{},[892,25804,25805],{},"Register webhook",[877,25807,25808,25811,25816,25818],{},[892,25809,25810],{},"7",[892,25812,25813],{},[895,25814,25815],{},"/document/{id}/download",[892,25817,25774],{},[892,25819,25820],{},"Download signed PDF",[842,25822,25823,25824,25827],{},"The full integration is about ",[996,25825,25826],{},"400 lines of Python"," across four files - a service layer, a model, two Celery tasks, and a webhook view. It plugs cleanly into any Django project that needs e-signature capabilities.",[1680,25829,25830],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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 .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 .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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s5tWE, html code.shiki .s5tWE{--shiki-light:#E53935;--shiki-light-font-style:italic;--shiki-default:#F07178;--shiki-default-font-style:italic;--shiki-dark:#F07178;--shiki-dark-font-style:italic}",{"title":728,"searchDepth":729,"depth":729,"links":25832},[25833,25834,25835,25836,25842,25843,25844,25845,25846,25847,25854],{"id":18817,"depth":729,"text":18818},{"id":18899,"depth":729,"text":18900},{"id":18975,"depth":729,"text":18976},{"id":19299,"depth":729,"text":19300,"children":25837},[25838,25839,25840,25841],{"id":19309,"depth":1112,"text":19310},{"id":20262,"depth":1112,"text":20263},{"id":21003,"depth":1112,"text":21004},{"id":21424,"depth":1112,"text":21425},{"id":21836,"depth":729,"text":21837},{"id":22600,"depth":729,"text":22601},{"id":23913,"depth":729,"text":23914},{"id":24770,"depth":729,"text":24771},{"id":25158,"depth":729,"text":25159},{"id":25635,"depth":729,"text":25636,"children":25848},[25849,25850,25851,25852,25853],{"id":25642,"depth":1112,"text":25643},{"id":25656,"depth":1112,"text":25657},{"id":25663,"depth":1112,"text":25664},{"id":25677,"depth":1112,"text":25678},{"id":25684,"depth":1112,"text":25685},{"id":8196,"depth":729,"text":25699},"2026-02-13T00:00:00.000Z","A practical guide to adding e-signature capabilities to your Django app using the SignNow REST API. We walk through the full integration - from OAuth2 authentication and document uploads to asynchronous signing workflows with Celery and real-time webhook handling - based on our production implementation at BeatBuddy.",{"src":25858,"credit":25859},"/images/blog/musictechlab_blog_signnow_django_integration.webp","Photo by [Scott Graham](https://unsplash.com/@amstram) on [Unsplash](https://unsplash.com/photos/OQMZwNd3ThU)",{"enabled":738,"items":25861},[25862,25864,25866,25868],{"text":25863,"icon":5365},"The full SignNow Django integration is about 400 lines across four files.",{"text":25865,"icon":2939},"All SignNow API calls run in Celery tasks to keep the request cycle fast.",{"text":25867,"icon":1779},"Role-based invites need a two-step process: add fields, then re-fetch to get role IDs.",{"text":25869,"icon":3271},"Cache OAuth2 tokens in Redis with a 5-minute buffer to avoid extra round-trips.",{},{"title":542,"description":25856},[18784],"B2omIw3TRWUTJSZukF0UtP4lbpEdNuc7Vc86WdkLOqQ",{"id":25875,"title":293,"authors":25876,"badge":25882,"body":25884,"category":5678,"client":723,"date":26056,"description":5679,"extension":734,"faq":723,"featured":738,"featuredOrder":3515,"hidden":69,"image":26057,"keyTakeaways":723,"meta":26059,"navigation":738,"path":294,"seo":26060,"status":723,"stem":295,"tags":26061,"teaser":723,"__hash__":26063},"posts/blog/newsletter/musictech-insights-8-curated-by-kenny-vaughan.md",[25877],{"name":25878,"to":25879,"avatar":25880},"Kenny Vaughan","https://www.linkedin.com/in/kennyenricovaughan/",{"src":25881},"/images/partners/musictechlab_partners-kenny-vaughan.webp",{"label":25883,"color":5535},"#8",{"type":725,"value":25885,"toc":26047},[25886,25890,25896,25899,25901,25904,25909,25914,25920,25942,25944,25948,25951,25957,25963,25969,25971,25975,25978,25981,25987,25989,25993,25996,25999,26005,26007,26011,26014,26017,26020,26026,26028,26032,26035,26038,26041],[5539,25887,25889],{"id":25888},"musictech-insights-8-curated-by-kenny-vaughan","MusicTech Insights #8 | Curated by Kenny Vaughan",[842,25891,5545,25892,25895],{},[846,25893,25878],{"href":25879,"rel":25894},[850],", who works at the intersection of music, technology, and gaming. With over a decade of experience across music licensing, supervision, partnerships, and product strategy, Kenny brings a unique perspective on how creative ambition meets real‑world systems and processes.",[842,25897,25898],{},"In this edition, Kenny dives into the evolving role of music in gaming and interactive experiences, sharing sharp, contrarian insights on what drives cultural impact, how creative intent translates into scalable systems, and where the next opportunities in MusicTech lie. We’re excited to share his perspective and his pick of the most relevant stories shaping the industry today, and to get future issues of MusicTech Insights straight to your inbox, subscribe on our site.",[4937,25900],{},[863,25902,293],{"id":25903},"the-new-economics-of-game-music",[842,25905,25906],{},[964,25907,25908],{},"Hi, I’m Kenny Vaughan.",[842,25910,25911],{},[964,25912,25913],{},"I’ve spent my career tracking how music moves through culture as the systems around it change. From record shops and physical retail, through piracy and early digital platforms, into streaming at scale and social media recontextualisation, each shift has fragmented exposure while flattening intent. Alongside that, music in games has quietly existed as a cultural vessel for those who knew where to look, carrying identity outside of traditional marketing pathways.",[842,25915,25916,25919],{},[964,25917,25918],{},"What interests me now is how gamified and interactive platforms are bringing that idea to the surface. Games allow music to exist in new contexts rather than exclusively direct-to-fan feeds. I believe this shift will continue to reframe traditional marketing routes by creating smaller, more deliberate moments where music feels discovered rather than delivered.","_",[842,25921,25922,7826,25925,25932,7826,25935,25940],{},[964,25923,25924],{},"You can read my wider thoughts on my blog",[846,25926,25929],{"href":25927,"rel":25928},"https://betweenmusicandgaming.substack.com/",[850],[964,25930,25931],{},"Between Music & Gaming",[964,25933,25934],{},", or work with me via",[846,25936,25939],{"href":25937,"rel":25938},"https://www.linkedin.com/article/edit/7430257446411112448/?author=urn%3Ali%3Afsd_company%3A18041601#",[850],"Feel For Music",[964,25941,861],{},[4937,25943],{},[1074,25945,25947],{"id":25946},"what-happens-when-players-bring-their-own-music-into-games","What happens when players bring their own music into games",[842,25949,25950],{},"Music gains much of its power in games through context. The value comes from curation, where a deliberate selection of tracks becomes part of a game’s identity and is remembered alongside play. That association is what makes music in games feel meaningful rather than interchangeable.",[5583,25952],{"width":25953,"height":25954,"src":25955,"title":25956,"frameBorder":4417,"allow":5588,"referrerPolicy":5589,"allowFullScreen":738},560,315,"https://www.youtube.com/embed/3-hy4UkCRSc?si=Bkn_D3Swfo9HfRbO","YouTube video player",[842,25958,25959,25962],{},[846,25960,5070],{"href":25937,"rel":25961},[850]," integration introduces an interesting tension - giving players access to their entire catalogue offers freedom, but risks stripping away context and, with it, a sense of musical identity. If every game can sound like everything, the game itself risks sounding like nothing. The real question is how developers treat this integration, as a surface level feature, or as a curated system that still preserves intention and authorship.",[842,25964,25965],{},[846,25966,5605],{"href":25967,"rel":25968},"https://newsroom.spotify.com/2025-04-30/spotify-and-ea-sports-team-up-to-pilot-a-new-kind-of-audio-experience/",[850],[4937,25970],{},[1074,25972,25974],{"id":25973},"why-curated-music-will-define-quality-on-roblox","Why curated music will define quality on Roblox.",[842,25976,25977],{},"Roblox’s current music offering feels limited, not due to a lack of artists, but because meaningful music integration at scale depends on tracking and monetisation, something the platform has only partially addressed through its DistroKid partnership. Prioritising access over payment may lower friction in the short term, but it also sets an uneasy precedent for how music is valued inside interactive platforms.",[842,25979,25980],{},"That said, this feels more like a starting point than an endpoint. Music has already shifted into the background of daily life while still shaping how experiences feel, and Roblox presents a clear opportunity to do better. If music were treated as a tracked, intentional part of the system, it would raise the quality of games while allowing artists and developers to benefit together. Improving the overall sensory experience through sound is a path toward more refined, premium experiences, not away from them. I believe this will eventually be the clearest indicator of quality of first impression beyond commitment to gameplay for Roblox games, making sound one of the clearest early indicators of perceived quality, before gameplay has time to prove itself.",[842,25982,25983],{},[846,25984,5605],{"href":25985,"rel":25986},"https://www.musicbusinessworldwide.com/roblox-strikes-deal-with-distrokid-but-indie-artists-wont-be-paid-any-money-from-it1/",[850],[4937,25988],{},[1074,25990,25992],{"id":25991},"music-catalogues-becoming-private-equity-funds","Music catalogues becoming private equity funds.",[842,25994,25995],{},"Music catalogues are increasingly being treated like private equity assets. What began with Hipgnosis has expanded into a broader shift, with major labels focusing less on distribution and more on acquiring and holding rights at scale. The old bottlenecks of physical retail and early digital platforms have disappeared, leaving a market flooded with new releases and discovery driven by recontextualisation across social media, film, and games rather than artist development alone.",[842,25997,25998],{},"As a result, ownership has become the priority. Era-defining catalogues such as the 60s, 70s and 80s offer long term, predictable value, while contemporary music is harder to anchor beyond the moments that surface it. Labels are positioning themselves to benefit from value created elsewhere, reinforcing a model where control of rights matters more than how or where the music is actually used.",[842,26000,26001],{},[846,26002,5605],{"href":26003,"rel":26004},"https://www.theguardian.com/music/2025/dec/16/musicians-are-deeply-concerned-about-ai-so-why-are-the-major-labels-embracing-it",[850],[4937,26006],{},[1074,26008,26010],{"id":26009},"ai-driven-selection-and-its-impact-on-game-music","AI-driven selection and its impact on game music",[842,26012,26013],{},"The rise of agentic AI commerce introduces a new decision-maker into the creative economy, and it shifts decision-making further away from individual intent and toward systems-level optimisation. Google’s Universal Commerce Protocol actively solves your retail communication for you, being able to select and transact on behalf of users.",[842,26015,26016],{},"For music and games, this reframes how music assets are found and valued for commercial utility. When selection is driven by AI Agents, it makes sense they will categorise their selection by eligibility, rights clarity, price and contextual fit - highlighting a risk that they will repeatedly favour what is easiest to understand.",[842,26018,26019],{},"The very nature of the music industry relied on A&R’s going to grass roots venues and having an inspired belief that the artist they are watching can contribute to a wider musical culture if nurtured. Game developers that are not privy to the music supervision language will end up searching by broader, less specific terms regardless - potentially having the same tapering issue. This looming technical solution only proves a greater value for music supervision, ensuring that there will be a clearer difference between common tracks and themes versus curated and selected ones using professional music supervision, heightening the fan experience and perceived value of a game in comparison to the masses.",[842,26021,26022],{},[846,26023,5605],{"href":26024,"rel":26025},"https://blog.google/products/ads-commerce/agentic-commerce-ai-tools-protocol-retailers-platforms/",[850],[4937,26027],{},[1074,26029,26031],{"id":26030},"how-fans-reshape-music-beyond-the-game-through-fan-refraction","How fans reshape music beyond the game through Fan Refraction.",[842,26033,26034],{},"Fan engagement of game music is becoming less linear as the years go by. Rather than moving from discovery to fandom in a straight line, audiences now refract across platforms, contexts, and identities, interacting with music in fragmented but personal, meaningful ways. A track might first be encountered in a game, and resurface on social media in an entirely different way. But it’s also the case that a clip from a game may be shared with entirely new music through the lens of the fan.",[842,26036,26037],{},"A game needs to have a strong aesthetic (of its artwork as well as its musical identity), so it’s clear what juxtaposes it. This gives purpose to the fans of reimaginging the world in their own way as well as the game still having a striking and defining presence in its own right.",[842,26039,26040],{},"I call this Fan Refraction.This uses music both curated by the game, as well as fan-made selections, as tools to showcase fandom and in turn use music like a ‘hashtag’. The opportunity lies in designing systems that allow music to reappear at different moments, carrying emotional residue forward. Fan refraction rewards music that travels well across environments, while exposing the limits of strategies that rely on controlling attention in a single place.",[842,26042,26043],{},[846,26044,5605],{"href":26045,"rel":26046},"https://betweenmusicandgaming.substack.com/p/fan-refraction-music-is-the-new-hashtag",[850],{"title":728,"searchDepth":729,"depth":729,"links":26048},[26049],{"id":25903,"depth":729,"text":293,"children":26050},[26051,26052,26053,26054,26055],{"id":25946,"depth":1112,"text":25947},{"id":25973,"depth":1112,"text":25974},{"id":25991,"depth":1112,"text":25992},{"id":26009,"depth":1112,"text":26010},{"id":26030,"depth":1112,"text":26031},"2026-02-10T00:00:00.000Z",{"src":26058},"/images/blog/musictechlab_blog_musictech-insights-8-kenny-vaughan.webp",{},{"title":293,"description":5679},[5678,26062],"music-production","-zyLPnuYXfO-wNC4L-uO6jEWI3pjEbdHnImBDbTN0AA",{"id":26065,"title":116,"authors":26066,"badge":26069,"body":26071,"category":731,"client":723,"date":27910,"description":27911,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":27912,"keyTakeaways":27914,"meta":27924,"navigation":738,"path":117,"seo":27925,"status":723,"stem":118,"tags":27926,"teaser":723,"__hash__":27928},"posts/blog/music-data/building-a-suno-remix-app-with-nuxt-and-firebase.md",[26067],{"name":834,"to":720,"avatar":26068},{"src":722},{"label":26070,"color":838},"AI Audio",{"type":725,"value":26072,"toc":27890},[26073,26085,26092,26094,26098,26106,26117,26136,26141,26143,26147,26154,26161,26199,26204,26257,26259,26261,26343,26345,26349,26352,26356,26363,26798,26804,26808,26811,27139,27159,27163,27166,27312,27319,27321,27325,27328,27354,27357,27363,27370,27376,27379,27385,27389,27396,27441,27444,27446,27450,27453,27610,27613,27619,27622,27643,27645,27649,27652,27723,27725,27729,27736,27742,27749,27758,27761,27763,27765,27797,27799,27803,27806,27838,27840,27844,27857,27864,27866,27870,27887],[842,26074,26075,26076,5660,26079,7378,26082,861],{},"Imagine uploading a raw audio recording and getting back a fully remixed version in a completely different genre - Pop to Jazz, Rock to Lo-fi - all powered by AI. That's exactly what we built as a proof of concept using ",[996,26077,26078],{},"Suno's AI",[996,26080,26081],{},"Nuxt 4",[996,26083,26084],{},"Firebase",[842,26086,26087,26088,26091],{},"In this article, we'll walk you through the architecture, key technical decisions, and code behind our ",[996,26089,26090],{},"Suno Remix PoC",". Whether you're a music tech enthusiast or a developer exploring AI-powered audio tools, this guide will help you build something similar.",[4937,26093],{},[863,26095,26097],{"id":26096},"what-is-suno","What is Suno?",[842,26099,26100,26105],{},[846,26101,26104],{"href":26102,"rel":26103},"https://suno.com",[850],"Suno"," is one of the most impressive AI music generation platforms available today. It can generate full songs from text prompts, create covers in different styles, extend existing tracks, and even separate stems (vocals vs. instruments).",[842,26107,26108,26109,26112,26113,26116],{},"The catch? ",[996,26110,26111],{},"Suno doesn't have an official public API."," There's no ",[895,26114,26115],{},"suno.com/developers"," portal or API key system. Instead, a growing ecosystem of third-party services wraps Suno's capabilities behind a REST API (Application Programming Interface).",[842,26118,26119,26120,26127,26128,26131,26132,26135],{},"For this PoC, we chose ",[846,26121,26124],{"href":26122,"rel":26123},"https://sunoapi.org",[850],[996,26125,26126],{},"sunoapi.org"," - the most documented third-party provider with support for the ",[895,26129,26130],{},"upload-cover"," endpoint, which is what enables the \"remix\" functionality. It costs approximately ",[996,26133,26134],{},"$0.005 per credit",", which is very affordable for experimentation.",[1572,26137,26138],{},[842,26139,26140],{},"All available Suno API options are unofficial. For a proof of concept this is perfectly fine, but keep an eye on Suno's official announcements if you plan to take this to production.",[4937,26142],{},[863,26144,26146],{"id":26145},"the-architecture","The Architecture",[842,26148,26149,26150,26153],{},"Here's the core challenge: Suno's API doesn't accept file uploads directly. Instead, it requires a ",[996,26151,26152],{},"public URL"," pointing to the audio file. This means we need an intermediary storage layer.",[842,26155,26156,26157,26160],{},"Our solution uses ",[996,26158,26159],{},"Firebase Storage"," as the bridge:",[991,26162,26163,26169,26175,26184,26193],{},[961,26164,26165,26168],{},[996,26166,26167],{},"User uploads a WAV/MP3 file"," → stored in Firebase Storage (client-side SDK)",[961,26170,26171,26174],{},[996,26172,26173],{},"Firebase returns a public download URL"," → this is the key piece Suno needs",[961,26176,26177,26183],{},[996,26178,26179,26180],{},"Browser calls ",[895,26181,26182],{},"POST /api/remix"," → our Nitro server route forwards the request to sunoapi.org with the API key",[961,26185,26186,26192],{},[996,26187,26188,26189],{},"Browser polls ",[895,26190,26191],{},"GET /api/remix/:taskId"," → our server route checks sunoapi.org for the remix status every 30 seconds",[961,26194,26195,26198],{},[996,26196,26197],{},"Remix is ready"," → the browser plays both original and remixed audio side-by-side using WaveSurfer.js",[842,26200,26201],{},[996,26202,26203],{},"Why this architecture?",[871,26205,26206,26216],{},[874,26207,26208],{},[877,26209,26210,26213],{},[880,26211,26212],{},"Decision",[880,26214,26215],{},"Reason",[887,26217,26218,26227,26237,26247],{},[877,26219,26220,26224],{},[892,26221,26222],{},[996,26223,26159],{},[892,26225,26226],{},"Gives us a publicly accessible URL for the uploaded WAV file",[877,26228,26229,26234],{},[892,26230,26231],{},[996,26232,26233],{},"Nitro server routes",[892,26235,26236],{},"Keeps the Suno API key on the server - never exposed to the browser",[877,26238,26239,26244],{},[892,26240,26241],{},[996,26242,26243],{},"Polling",[892,26245,26246],{},"Suno processes remixes asynchronously (1-3 min); we poll every 30 seconds",[877,26248,26249,26254],{},[892,26250,26251],{},[996,26252,26253],{},"WaveSurfer.js",[892,26255,26256],{},"Industry-standard waveform visualization for comparing original vs. remixed audio",[4937,26258],{},[863,26260,8484],{"id":8483},[871,26262,26263,26272],{},[874,26264,26265],{},[877,26266,26267,26269],{},[880,26268,16939],{},[880,26270,26271],{},"Technology",[887,26273,26274,26284,26294,26304,26315,26324,26334],{},[877,26275,26276,26279],{},[892,26277,26278],{},"Frontend",[892,26280,26281,26283],{},[996,26282,26081],{}," + Vue 3 + Nuxt UI",[877,26285,26286,26289],{},[892,26287,26288],{},"Styling",[892,26290,26291],{},[996,26292,26293],{},"Tailwind CSS 4",[877,26295,26296,26299],{},[892,26297,26298],{},"Audio Visualization",[892,26300,26301],{},[996,26302,26303],{},"WaveSurfer.js 7",[877,26305,26306,26309],{},[892,26307,26308],{},"Server",[892,26310,26311,26314],{},[996,26312,26313],{},"Nitro"," (built into Nuxt)",[877,26316,26317,26320],{},[892,26318,26319],{},"Storage",[892,26321,26322],{},[996,26323,26159],{},[877,26325,26326,26329],{},[892,26327,26328],{},"Hosting",[892,26330,26331],{},[996,26332,26333],{},"Firebase Hosting",[877,26335,26336,26338],{},[892,26337,11843],{},[892,26339,26340,26342],{},[996,26341,26126],{}," (third-party Suno proxy)",[4937,26344],{},[863,26346,26348],{"id":26347},"the-key-technical-challenge-upload-flow","The Key Technical Challenge: Upload Flow",[842,26350,26351],{},"The most interesting part of this project is how we bridge the gap between a user's local WAV file and Suno's requirement for a public URL.",[1074,26353,26355],{"id":26354},"step-1-upload-to-firebase-storage","Step 1: Upload to Firebase Storage",[842,26357,26358,26359,26362],{},"We created a ",[895,26360,26361],{},"useFirebaseStorage"," composable that handles the upload with progress tracking:",[1013,26364,26367],{"className":4602,"code":26365,"filename":26366,"language":4605,"meta":728,"style":728},"import { getStorage, ref as storageRef, uploadBytesResumable, getDownloadURL } from 'firebase/storage'\n\nasync function uploadWavFile(file: File): Promise\u003Cstring> {\n  const storage = getStorage(app)\n  const fileName = `${crypto.randomUUID()}.wav`\n  const fileRef = storageRef(storage, `uploads/${fileName}`)\n\n  const uploadTask = uploadBytesResumable(fileRef, file, {\n    contentType: 'audio/wav',\n  })\n\n  return new Promise((resolve, reject) => {\n    uploadTask.on('state_changed',\n      (snapshot) => {\n        // Track progress (0-100%)\n        uploadProgress.value = Math.round(\n          (snapshot.bytesTransferred / snapshot.totalBytes) * 100\n        )\n      },\n      reject,\n      async () => {\n        // Get the public download URL\n        const downloadUrl = await getDownloadURL(uploadTask.snapshot.ref)\n        resolve(downloadUrl)\n      }\n    )\n  })\n}\n","useFirebaseStorage.ts",[895,26368,26369,26411,26415,26446,26465,26495,26530,26534,26559,26575,26581,26585,26613,26634,26648,26653,26675,26704,26708,26713,26720,26732,26737,26767,26779,26784,26788,26794],{"__ignoreMap":728},[1086,26370,26371,26373,26375,26378,26380,26383,26385,26388,26390,26393,26395,26398,26400,26403,26406,26409],{"class":1088,"line":1089},[1086,26372,6503],{"class":1423},[1086,26374,4520],{"class":1146},[1086,26376,26377],{"class":1436}," getStorage",[1086,26379,1227],{"class":1146},[1086,26381,26382],{"class":1436}," ref",[1086,26384,19873],{"class":1423},[1086,26386,26387],{"class":1436}," storageRef",[1086,26389,1227],{"class":1146},[1086,26391,26392],{"class":1436}," uploadBytesResumable",[1086,26394,1227],{"class":1146},[1086,26396,26397],{"class":1436}," getDownloadURL",[1086,26399,4690],{"class":1146},[1086,26401,26402],{"class":1423}," from",[1086,26404,26405],{"class":1146}," '",[1086,26407,26408],{"class":1096},"firebase/storage",[1086,26410,25592],{"class":1146},[1086,26412,26413],{"class":1088,"line":729},[1086,26414,3390],{"emptyLinePlaceholder":738},[1086,26416,26417,26420,26422,26425,26427,26429,26431,26434,26436,26438,26440,26442,26444],{"class":1088,"line":1112},[1086,26418,26419],{"class":1155},"async",[1086,26421,4617],{"class":1155},[1086,26423,26424],{"class":1105}," uploadWavFile",[1086,26426,1398],{"class":1146},[1086,26428,8953],{"class":1401},[1086,26430,1133],{"class":1146},[1086,26432,26433],{"class":1092}," File",[1086,26435,4723],{"class":1146},[1086,26437,4626],{"class":1092},[1086,26439,11164],{"class":1146},[1086,26441,18489],{"class":1092},[1086,26443,2694],{"class":1146},[1086,26445,1164],{"class":1146},[1086,26447,26448,26451,26454,26456,26458,26460,26463],{"class":1088,"line":1181},[1086,26449,26450],{"class":1155},"  const",[1086,26452,26453],{"class":1436}," storage",[1086,26455,19552],{"class":1146},[1086,26457,26377],{"class":1105},[1086,26459,1398],{"class":4109},[1086,26461,26462],{"class":1436},"app",[1086,26464,1455],{"class":4109},[1086,26466,26467,26469,26472,26474,26477,26480,26482,26485,26487,26489,26492],{"class":1088,"line":1205},[1086,26468,26450],{"class":1155},[1086,26470,26471],{"class":1436}," fileName",[1086,26473,19552],{"class":1146},[1086,26475,26476],{"class":1146}," `${",[1086,26478,26479],{"class":1436},"crypto",[1086,26481,861],{"class":1146},[1086,26483,26484],{"class":1105},"randomUUID",[1086,26486,2516],{"class":1436},[1086,26488,4423],{"class":1146},[1086,26490,26491],{"class":1096},".wav",[1086,26493,26494],{"class":1146},"`\n",[1086,26496,26497,26499,26502,26504,26506,26508,26511,26513,26516,26519,26522,26525,26528],{"class":1088,"line":1276},[1086,26498,26450],{"class":1155},[1086,26500,26501],{"class":1436}," fileRef",[1086,26503,19552],{"class":1146},[1086,26505,26387],{"class":1105},[1086,26507,1398],{"class":4109},[1086,26509,26510],{"class":1436},"storage",[1086,26512,1227],{"class":1146},[1086,26514,26515],{"class":1146}," `",[1086,26517,26518],{"class":1096},"uploads/",[1086,26520,26521],{"class":1146},"${",[1086,26523,26524],{"class":1436},"fileName",[1086,26526,26527],{"class":1146},"}`",[1086,26529,1455],{"class":4109},[1086,26531,26532],{"class":1088,"line":1282},[1086,26533,3390],{"emptyLinePlaceholder":738},[1086,26535,26536,26538,26541,26543,26545,26547,26550,26552,26555,26557],{"class":1088,"line":1288},[1086,26537,26450],{"class":1155},[1086,26539,26540],{"class":1436}," uploadTask",[1086,26542,19552],{"class":1146},[1086,26544,26392],{"class":1105},[1086,26546,1398],{"class":4109},[1086,26548,26549],{"class":1436},"fileRef",[1086,26551,1227],{"class":1146},[1086,26553,26554],{"class":1436}," file",[1086,26556,1227],{"class":1146},[1086,26558,1164],{"class":1146},[1086,26560,26561,26564,26566,26568,26571,26573],{"class":1088,"line":2685},[1086,26562,26563],{"class":4109},"    contentType",[1086,26565,1133],{"class":1146},[1086,26567,26405],{"class":1146},[1086,26569,26570],{"class":1096},"audio/wav",[1086,26572,10742],{"class":1146},[1086,26574,1202],{"class":1146},[1086,26576,26577,26579],{"class":1088,"line":2700},[1086,26578,4797],{"class":1146},[1086,26580,1455],{"class":4109},[1086,26582,26583],{"class":1088,"line":3398},[1086,26584,3390],{"emptyLinePlaceholder":738},[1086,26586,26587,26589,26592,26594,26596,26598,26601,26603,26606,26608,26611],{"class":1088,"line":1715},[1086,26588,4656],{"class":1423},[1086,26590,26591],{"class":1146}," new",[1086,26593,4626],{"class":1092},[1086,26595,1398],{"class":4109},[1086,26597,1398],{"class":1146},[1086,26599,26600],{"class":1401},"resolve",[1086,26602,1227],{"class":1146},[1086,26604,26605],{"class":1401}," reject",[1086,26607,1410],{"class":1146},[1086,26609,26610],{"class":1155}," =>",[1086,26612,1164],{"class":1146},[1086,26614,26615,26618,26620,26623,26625,26627,26630,26632],{"class":1088,"line":3409},[1086,26616,26617],{"class":1436},"    uploadTask",[1086,26619,861],{"class":1146},[1086,26621,26622],{"class":1105},"on",[1086,26624,1398],{"class":4109},[1086,26626,10742],{"class":1146},[1086,26628,26629],{"class":1096},"state_changed",[1086,26631,10742],{"class":1146},[1086,26633,1202],{"class":1146},[1086,26635,26636,26639,26642,26644,26646],{"class":1088,"line":3415},[1086,26637,26638],{"class":1146},"      (",[1086,26640,26641],{"class":1401},"snapshot",[1086,26643,1410],{"class":1146},[1086,26645,26610],{"class":1155},[1086,26647,1164],{"class":1146},[1086,26649,26650],{"class":1088,"line":3421},[1086,26651,26652],{"class":1427},"        // Track progress (0-100%)\n",[1086,26654,26655,26658,26660,26663,26665,26668,26670,26673],{"class":1088,"line":3427},[1086,26656,26657],{"class":1436},"        uploadProgress",[1086,26659,861],{"class":1146},[1086,26661,26662],{"class":1436},"value",[1086,26664,19552],{"class":1146},[1086,26666,26667],{"class":1436}," Math",[1086,26669,861],{"class":1146},[1086,26671,26672],{"class":1105},"round",[1086,26674,4094],{"class":4109},[1086,26676,26677,26680,26682,26684,26687,26690,26693,26695,26698,26700,26702],{"class":1088,"line":3433},[1086,26678,26679],{"class":4109},"          (",[1086,26681,26641],{"class":1436},[1086,26683,861],{"class":1146},[1086,26685,26686],{"class":1436},"bytesTransferred",[1086,26688,26689],{"class":1146}," /",[1086,26691,26692],{"class":1436}," snapshot",[1086,26694,861],{"class":1146},[1086,26696,26697],{"class":1436},"totalBytes",[1086,26699,2778],{"class":4109},[1086,26701,2775],{"class":1146},[1086,26703,6364],{"class":1187},[1086,26705,26706],{"class":1088,"line":3439},[1086,26707,4133],{"class":4109},[1086,26709,26710],{"class":1088,"line":3444},[1086,26711,26712],{"class":1146},"      },\n",[1086,26714,26715,26718],{"class":1088,"line":3450},[1086,26716,26717],{"class":1436},"      reject",[1086,26719,1202],{"class":1146},[1086,26721,26722,26725,26728,26730],{"class":1088,"line":3456},[1086,26723,26724],{"class":1155},"      async",[1086,26726,26727],{"class":1146}," ()",[1086,26729,26610],{"class":1155},[1086,26731,1164],{"class":1146},[1086,26733,26734],{"class":1088,"line":3462},[1086,26735,26736],{"class":1427},"        // Get the public download URL\n",[1086,26738,26739,26742,26745,26747,26749,26751,26753,26756,26758,26760,26762,26765],{"class":1088,"line":3467},[1086,26740,26741],{"class":1155},"        const",[1086,26743,26744],{"class":1436}," downloadUrl",[1086,26746,19552],{"class":1146},[1086,26748,4659],{"class":1423},[1086,26750,26397],{"class":1105},[1086,26752,1398],{"class":4109},[1086,26754,26755],{"class":1436},"uploadTask",[1086,26757,861],{"class":1146},[1086,26759,26641],{"class":1436},[1086,26761,861],{"class":1146},[1086,26763,26764],{"class":1436},"ref",[1086,26766,1455],{"class":4109},[1086,26768,26769,26772,26774,26777],{"class":1088,"line":3473},[1086,26770,26771],{"class":1105},"        resolve",[1086,26773,1398],{"class":4109},[1086,26775,26776],{"class":1436},"downloadUrl",[1086,26778,1455],{"class":4109},[1086,26780,26781],{"class":1088,"line":3479},[1086,26782,26783],{"class":1146},"      }\n",[1086,26785,26786],{"class":1088,"line":3485},[1086,26787,6219],{"class":4109},[1086,26789,26790,26792],{"class":1088,"line":3491},[1086,26791,4797],{"class":1146},[1086,26793,1455],{"class":4109},[1086,26795,26796],{"class":1088,"line":3497},[1086,26797,1291],{"class":1146},[842,26799,5119,26800,26803],{},[895,26801,26802],{},"getDownloadURL()"," call returns a publicly accessible Firebase Storage URL - exactly what Suno needs.",[1074,26805,26807],{"id":26806},"step-2-call-the-suno-api-via-server-proxy","Step 2: Call the Suno API (via server proxy)",[842,26809,26810],{},"We never call Suno directly from the browser. Instead, we use a Nitro server route that reads the API key from server-only runtime configuration:",[1013,26812,26815],{"className":4602,"code":26813,"filename":26814,"language":4605,"meta":728,"style":728},"export default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n  const config = useRuntimeConfig()\n\n  const result = await fetch('https://api.sunoapi.org/api/v1/generate/upload-cover', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${config.sunoApiKey}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({\n      uploadUrl: body.uploadUrl,\n      customMode: true,\n      style: body.style,\n      title: body.title,\n      model: 'V4_5',\n    }),\n  })\n\n  const data = await result.json()\n  return { taskId: data.data.taskId }\n})\n","server/api/remix.post.ts",[895,26816,26817,26840,26860,26874,26878,26904,26918,26926,26956,26974,26978,26996,27012,27025,27040,27055,27071,27080,27086,27090,27108,27133],{"__ignoreMap":728},[1086,26818,26819,26821,26823,26826,26828,26830,26832,26834,26836,26838],{"class":1088,"line":1089},[1086,26820,3625],{"class":1423},[1086,26822,19130],{"class":1423},[1086,26824,26825],{"class":1105}," defineEventHandler",[1086,26827,1398],{"class":1436},[1086,26829,26419],{"class":1155},[1086,26831,5979],{"class":1146},[1086,26833,21543],{"class":1401},[1086,26835,1410],{"class":1146},[1086,26837,26610],{"class":1155},[1086,26839,1164],{"class":1146},[1086,26841,26842,26844,26847,26849,26851,26854,26856,26858],{"class":1088,"line":729},[1086,26843,26450],{"class":1155},[1086,26845,26846],{"class":1436}," body",[1086,26848,19552],{"class":1146},[1086,26850,4659],{"class":1423},[1086,26852,26853],{"class":1105}," readBody",[1086,26855,1398],{"class":4109},[1086,26857,21543],{"class":1436},[1086,26859,1455],{"class":4109},[1086,26861,26862,26864,26867,26869,26872],{"class":1088,"line":1112},[1086,26863,26450],{"class":1155},[1086,26865,26866],{"class":1436}," config",[1086,26868,19552],{"class":1146},[1086,26870,26871],{"class":1105}," useRuntimeConfig",[1086,26873,1387],{"class":4109},[1086,26875,26876],{"class":1088,"line":1181},[1086,26877,3390],{"emptyLinePlaceholder":738},[1086,26879,26880,26882,26884,26886,26888,26891,26893,26895,26898,26900,26902],{"class":1088,"line":1205},[1086,26881,26450],{"class":1155},[1086,26883,23102],{"class":1436},[1086,26885,19552],{"class":1146},[1086,26887,4659],{"class":1423},[1086,26889,26890],{"class":1105}," fetch",[1086,26892,1398],{"class":4109},[1086,26894,10742],{"class":1146},[1086,26896,26897],{"class":1096},"https://api.sunoapi.org/api/v1/generate/upload-cover",[1086,26899,10742],{"class":1146},[1086,26901,1227],{"class":1146},[1086,26903,1164],{"class":1146},[1086,26905,26906,26908,26910,26912,26914,26916],{"class":1088,"line":1276},[1086,26907,4762],{"class":4109},[1086,26909,1133],{"class":1146},[1086,26911,26405],{"class":1146},[1086,26913,4685],{"class":1096},[1086,26915,10742],{"class":1146},[1086,26917,1202],{"class":1146},[1086,26919,26920,26922,26924],{"class":1088,"line":1282},[1086,26921,17650],{"class":4109},[1086,26923,1133],{"class":1146},[1086,26925,1164],{"class":1146},[1086,26927,26928,26931,26933,26935,26937,26939,26942,26944,26947,26949,26952,26954],{"class":1088,"line":1288},[1086,26929,26930],{"class":1146},"      '",[1086,26932,17211],{"class":4109},[1086,26934,10742],{"class":1146},[1086,26936,1133],{"class":1146},[1086,26938,26515],{"class":1146},[1086,26940,26941],{"class":1096},"Bearer ",[1086,26943,26521],{"class":1146},[1086,26945,26946],{"class":1436},"config",[1086,26948,861],{"class":1146},[1086,26950,26951],{"class":1436},"sunoApiKey",[1086,26953,26527],{"class":1146},[1086,26955,1202],{"class":1146},[1086,26957,26958,26960,26962,26964,26966,26968,26970,26972],{"class":1088,"line":2685},[1086,26959,26930],{"class":1146},[1086,26961,20056],{"class":4109},[1086,26963,10742],{"class":1146},[1086,26965,1133],{"class":1146},[1086,26967,26405],{"class":1146},[1086,26969,20739],{"class":1096},[1086,26971,10742],{"class":1146},[1086,26973,1202],{"class":1146},[1086,26975,26976],{"class":1088,"line":2700},[1086,26977,7313],{"class":1146},[1086,26979,26980,26982,26984,26987,26989,26992,26994],{"class":1088,"line":3398},[1086,26981,4777],{"class":4109},[1086,26983,1133],{"class":1146},[1086,26985,26986],{"class":1436}," JSON",[1086,26988,861],{"class":1146},[1086,26990,26991],{"class":1105},"stringify",[1086,26993,1398],{"class":4109},[1086,26995,1147],{"class":1146},[1086,26997,26998,27001,27003,27005,27007,27010],{"class":1088,"line":1715},[1086,26999,27000],{"class":4109},"      uploadUrl",[1086,27002,1133],{"class":1146},[1086,27004,26846],{"class":1436},[1086,27006,861],{"class":1146},[1086,27008,27009],{"class":1436},"uploadUrl",[1086,27011,1202],{"class":1146},[1086,27013,27014,27017,27019,27023],{"class":1088,"line":3409},[1086,27015,27016],{"class":4109},"      customMode",[1086,27018,1133],{"class":1146},[1086,27020,27022],{"class":27021},"sfNiH"," true",[1086,27024,1202],{"class":1146},[1086,27026,27027,27030,27032,27034,27036,27038],{"class":1088,"line":3415},[1086,27028,27029],{"class":4109},"      style",[1086,27031,1133],{"class":1146},[1086,27033,26846],{"class":1436},[1086,27035,861],{"class":1146},[1086,27037,1680],{"class":1436},[1086,27039,1202],{"class":1146},[1086,27041,27042,27045,27047,27049,27051,27053],{"class":1088,"line":3421},[1086,27043,27044],{"class":4109},"      title",[1086,27046,1133],{"class":1146},[1086,27048,26846],{"class":1436},[1086,27050,861],{"class":1146},[1086,27052,9069],{"class":1436},[1086,27054,1202],{"class":1146},[1086,27056,27057,27060,27062,27064,27067,27069],{"class":1088,"line":3427},[1086,27058,27059],{"class":4109},"      model",[1086,27061,1133],{"class":1146},[1086,27063,26405],{"class":1146},[1086,27065,27066],{"class":1096},"V4_5",[1086,27068,10742],{"class":1146},[1086,27070,1202],{"class":1146},[1086,27072,27073,27076,27078],{"class":1088,"line":3433},[1086,27074,27075],{"class":1146},"    }",[1086,27077,1410],{"class":4109},[1086,27079,1202],{"class":1146},[1086,27081,27082,27084],{"class":1088,"line":3439},[1086,27083,4797],{"class":1146},[1086,27085,1455],{"class":4109},[1086,27087,27088],{"class":1088,"line":3444},[1086,27089,3390],{"emptyLinePlaceholder":738},[1086,27091,27092,27094,27096,27098,27100,27102,27104,27106],{"class":1088,"line":3450},[1086,27093,26450],{"class":1155},[1086,27095,20125],{"class":1436},[1086,27097,19552],{"class":1146},[1086,27099,4659],{"class":1423},[1086,27101,23102],{"class":1436},[1086,27103,861],{"class":1146},[1086,27105,1139],{"class":1105},[1086,27107,1387],{"class":4109},[1086,27109,27110,27112,27114,27117,27119,27121,27123,27125,27127,27130],{"class":1088,"line":3456},[1086,27111,4656],{"class":1423},[1086,27113,4520],{"class":1146},[1086,27115,27116],{"class":4109}," taskId",[1086,27118,1133],{"class":1146},[1086,27120,20125],{"class":1436},[1086,27122,861],{"class":1146},[1086,27124,4337],{"class":1436},[1086,27126,861],{"class":1146},[1086,27128,27129],{"class":1436},"taskId",[1086,27131,27132],{"class":1146}," }\n",[1086,27134,27135,27137],{"class":1088,"line":3462},[1086,27136,4423],{"class":1146},[1086,27138,1455],{"class":1436},[1032,27140,27141],{},[842,27142,5119,27143,27146,27147,27150,27151,27154,27155,27158],{},[895,27144,27145],{},"NUXT_SUNO_API_KEY"," environment variable is mapped to ",[895,27148,27149],{},"runtimeConfig.sunoApiKey"," (without ",[895,27152,27153],{},"public","), which means Nuxt ",[996,27156,27157],{},"never bundles it into client-side JavaScript",". This is the correct way to handle API secrets in Nuxt.",[1074,27160,27162],{"id":27161},"step-3-poll-for-results","Step 3: Poll for Results",[842,27164,27165],{},"Suno processes remixes asynchronously. We poll every 30 seconds:",[1013,27167,27170],{"className":4602,"code":27168,"filename":27169,"language":4605,"meta":728,"style":728},"pollInterval = setInterval(async () => {\n  const result = await $fetch(`/api/remix/${taskId.value}`)\n\n  if (result.status === 'SUCCESS') {\n    tracks.value = result.tracks\n    status.value = 'completed'\n    stopPolling()\n  }\n}, 30000)\n","useSunoRemix.ts",[895,27171,27172,27192,27225,27229,27256,27274,27292,27299,27303],{"__ignoreMap":728},[1086,27173,27174,27177,27179,27182,27184,27186,27188,27190],{"class":1088,"line":1089},[1086,27175,27176],{"class":1436},"pollInterval ",[1086,27178,1440],{"class":1146},[1086,27180,27181],{"class":1105}," setInterval",[1086,27183,1398],{"class":1436},[1086,27185,26419],{"class":1155},[1086,27187,26727],{"class":1146},[1086,27189,26610],{"class":1155},[1086,27191,1164],{"class":1146},[1086,27193,27194,27196,27198,27200,27202,27205,27207,27210,27213,27215,27217,27219,27221,27223],{"class":1088,"line":729},[1086,27195,26450],{"class":1155},[1086,27197,23102],{"class":1436},[1086,27199,19552],{"class":1146},[1086,27201,4659],{"class":1423},[1086,27203,27204],{"class":1105}," $fetch",[1086,27206,1398],{"class":4109},[1086,27208,27209],{"class":1146},"`",[1086,27211,27212],{"class":1096},"/api/remix/",[1086,27214,26521],{"class":1146},[1086,27216,27129],{"class":1436},[1086,27218,861],{"class":1146},[1086,27220,26662],{"class":1436},[1086,27222,26527],{"class":1146},[1086,27224,1455],{"class":4109},[1086,27226,27227],{"class":1088,"line":1112},[1086,27228,3390],{"emptyLinePlaceholder":738},[1086,27230,27231,27234,27236,27238,27240,27242,27245,27247,27250,27252,27254],{"class":1088,"line":1181},[1086,27232,27233],{"class":1423},"  if",[1086,27235,5979],{"class":4109},[1086,27237,1473],{"class":1436},[1086,27239,861],{"class":1146},[1086,27241,11116],{"class":1436},[1086,27243,27244],{"class":1146}," ===",[1086,27246,26405],{"class":1146},[1086,27248,27249],{"class":1096},"SUCCESS",[1086,27251,10742],{"class":1146},[1086,27253,2778],{"class":4109},[1086,27255,1147],{"class":1146},[1086,27257,27258,27261,27263,27265,27267,27269,27271],{"class":1088,"line":1205},[1086,27259,27260],{"class":1436},"    tracks",[1086,27262,861],{"class":1146},[1086,27264,26662],{"class":1436},[1086,27266,19552],{"class":1146},[1086,27268,23102],{"class":1436},[1086,27270,861],{"class":1146},[1086,27272,27273],{"class":1436},"tracks\n",[1086,27275,27276,27279,27281,27283,27285,27287,27290],{"class":1088,"line":1276},[1086,27277,27278],{"class":1436},"    status",[1086,27280,861],{"class":1146},[1086,27282,26662],{"class":1436},[1086,27284,19552],{"class":1146},[1086,27286,26405],{"class":1146},[1086,27288,27289],{"class":1096},"completed",[1086,27291,25592],{"class":1146},[1086,27293,27294,27297],{"class":1088,"line":1282},[1086,27295,27296],{"class":1105},"    stopPolling",[1086,27298,1387],{"class":4109},[1086,27300,27301],{"class":1088,"line":1288},[1086,27302,1285],{"class":1146},[1086,27304,27305,27307,27310],{"class":1088,"line":2685},[1086,27306,24265],{"class":1146},[1086,27308,27309],{"class":1187}," 30000",[1086,27311,1455],{"class":1436},[842,27313,27314,27315,27318],{},"When the remix is ready, the response includes an ",[895,27316,27317],{},"audio_url"," pointing to the generated track on Suno's CDN (Content Delivery Network).",[4937,27320],{},[863,27322,27324],{"id":27323},"building-the-frontend","Building the Frontend",[842,27326,27327],{},"The UI is intentionally simple - this is a PoC, after all. The entire flow happens on a single page:",[991,27329,27330,27336,27342,27348],{},[961,27331,27332,27335],{},[996,27333,27334],{},"UploadZone"," - Drag-and-drop or file picker with WAV/MP3 validation (max 50 MB)",[961,27337,27338,27341],{},[996,27339,27340],{},"StyleSelector"," - Genre dropdown (16 genres), title input, and instrumental toggle",[961,27343,27344,27347],{},[996,27345,27346],{},"ProgressIndicator"," - 3-step progress bar with upload percentage",[961,27349,27350,27353],{},[996,27351,27352],{},"AudioComparison"," - Side-by-side WaveSurfer.js players for original and remixed audio",[842,27355,27356],{},"Here's what the upload screen looks like:",[842,27358,27359],{},[1027,27360],{"alt":27361,"src":27362},"Upload & Configure - drag-drop zone with genre selector and Remix button","/images/blog/suno-remix-poc-upload.webp",[842,27364,27365,27366,27369],{},"Once you hit ",[996,27367,27368],{},"Remix",", the app uploads the file to Firebase Storage and kicks off the AI processing. The 3-step progress indicator keeps you informed:",[842,27371,27372],{},[1027,27373],{"alt":27374,"src":27375},"Progress indicator - Upload to Cloud → AI Remix → Done","/images/blog/suno-remix-poc-progress.webp",[842,27377,27378],{},"When the remix is complete, you get a side-by-side comparison with waveform visualization - the original on the left, and the AI-generated remix(es) alongside it:",[842,27380,27381],{},[1027,27382],{"alt":27383,"src":27384},"Results - original and remixed audio with WaveSurfer.js waveforms","/images/blog/suno-remix-poc-results.webp",[1074,27386,27388],{"id":27387},"wavesurferjs-integration","WaveSurfer.js Integration",[842,27390,27391,27392,27395],{},"We wrapped WaveSurfer.js in a Vue component with ",[895,27393,27394],{},"\u003CClientOnly>"," to avoid SSR (Server-Side Rendering) issues:",[1013,27397,27402],{"className":27398,"code":27399,"filename":27400,"language":27401,"meta":728,"style":728},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003CClientOnly>\n  \u003CWaveformPlayer\n    :audio-url=\"track.audioUrl\"\n    label=\"Remix - Jazz\"\n  />\n\u003C/ClientOnly>\n","WaveformPlayer.vue","vue",[895,27403,27404,27413,27418,27423,27428,27433],{"__ignoreMap":728},[1086,27405,27406,27408,27411],{"class":1088,"line":1089},[1086,27407,11164],{"class":1146},[1086,27409,27410],{"class":4109},"ClientOnly",[1086,27412,11170],{"class":1146},[1086,27414,27415],{"class":1088,"line":729},[1086,27416,27417],{"class":1436},"  \u003CWaveformPlayer\n",[1086,27419,27420],{"class":1088,"line":1112},[1086,27421,27422],{"class":1436},"    :audio-url=\"track.audioUrl\"\n",[1086,27424,27425],{"class":1088,"line":1181},[1086,27426,27427],{"class":1436},"    label=\"Remix - Jazz\"\n",[1086,27429,27430],{"class":1088,"line":1205},[1086,27431,27432],{"class":1436},"  />\n",[1086,27434,27435,27437,27439],{"class":1088,"line":1276},[1086,27436,11201],{"class":1146},[1086,27438,27410],{"class":4109},[1086,27440,11170],{"class":1146},[842,27442,27443],{},"The player provides play/pause controls, a progress bar, and time display - all styled to match the dark theme.",[4937,27445],{},[863,27447,27449],{"id":27448},"deployment-to-firebase","Deployment to Firebase",[842,27451,27452],{},"Firebase Hosting serves the Nuxt static output. The setup is straightforward:",[1013,27454,27457],{"className":1136,"code":27455,"filename":27456,"language":1139,"meta":728,"style":728},"{\n  \"hosting\": {\n    \"public\": \".output/public\",\n    \"cleanUrls\": true,\n    \"rewrites\": [\n      { \"source\": \"**\", \"destination\": \"/index.html\" }\n    ]\n  },\n  \"storage\": {\n    \"rules\": \"firebase-storage.rules\"\n  }\n}\n","firebase.json",[895,27458,27459,27463,27476,27495,27508,27521,27562,27567,27572,27584,27602,27606],{"__ignoreMap":728},[1086,27460,27461],{"class":1088,"line":1089},[1086,27462,1147],{"class":1146},[1086,27464,27465,27467,27470,27472,27474],{"class":1088,"line":729},[1086,27466,1152],{"class":1146},[1086,27468,27469],{"class":1155},"hosting",[1086,27471,1159],{"class":1146},[1086,27473,1133],{"class":1146},[1086,27475,1164],{"class":1146},[1086,27477,27478,27480,27482,27484,27486,27488,27491,27493],{"class":1088,"line":1112},[1086,27479,1169],{"class":1146},[1086,27481,27153],{"class":1092},[1086,27483,1159],{"class":1146},[1086,27485,1133],{"class":1146},[1086,27487,1195],{"class":1146},[1086,27489,27490],{"class":1096},".output/public",[1086,27492,1159],{"class":1146},[1086,27494,1202],{"class":1146},[1086,27496,27497,27499,27502,27504,27506],{"class":1088,"line":1181},[1086,27498,1169],{"class":1146},[1086,27500,27501],{"class":1092},"cleanUrls",[1086,27503,1159],{"class":1146},[1086,27505,1133],{"class":1146},[1086,27507,9267],{"class":1146},[1086,27509,27510,27512,27515,27517,27519],{"class":1088,"line":1205},[1086,27511,1169],{"class":1146},[1086,27513,27514],{"class":1092},"rewrites",[1086,27516,1159],{"class":1146},[1086,27518,1133],{"class":1146},[1086,27520,6580],{"class":1146},[1086,27522,27523,27526,27528,27531,27533,27535,27537,27540,27542,27544,27546,27549,27551,27553,27555,27558,27560],{"class":1088,"line":1276},[1086,27524,27525],{"class":1146},"      {",[1086,27527,1195],{"class":1146},[1086,27529,27530],{"class":1187},"source",[1086,27532,1159],{"class":1146},[1086,27534,1133],{"class":1146},[1086,27536,1195],{"class":1146},[1086,27538,27539],{"class":1096},"**",[1086,27541,1159],{"class":1146},[1086,27543,1227],{"class":1146},[1086,27545,1195],{"class":1146},[1086,27547,27548],{"class":1187},"destination",[1086,27550,1159],{"class":1146},[1086,27552,1133],{"class":1146},[1086,27554,1195],{"class":1146},[1086,27556,27557],{"class":1096},"/index.html",[1086,27559,1159],{"class":1146},[1086,27561,27132],{"class":1146},[1086,27563,27564],{"class":1088,"line":1282},[1086,27565,27566],{"class":1146},"    ]\n",[1086,27568,27569],{"class":1088,"line":1288},[1086,27570,27571],{"class":1146},"  },\n",[1086,27573,27574,27576,27578,27580,27582],{"class":1088,"line":2685},[1086,27575,1152],{"class":1146},[1086,27577,26510],{"class":1155},[1086,27579,1159],{"class":1146},[1086,27581,1133],{"class":1146},[1086,27583,1164],{"class":1146},[1086,27585,27586,27588,27591,27593,27595,27597,27600],{"class":1088,"line":2700},[1086,27587,1169],{"class":1146},[1086,27589,27590],{"class":1092},"rules",[1086,27592,1159],{"class":1146},[1086,27594,1133],{"class":1146},[1086,27596,1195],{"class":1146},[1086,27598,27599],{"class":1096},"firebase-storage.rules",[1086,27601,4441],{"class":1146},[1086,27603,27604],{"class":1088,"line":3398},[1086,27605,1285],{"class":1146},[1086,27607,27608],{"class":1088,"line":1715},[1086,27609,1291],{"class":1146},[842,27611,27612],{},"Firebase Storage rules restrict uploads to WAV and MP3 files under 50 MB:",[1013,27614,27617],{"className":27615,"code":27616,"language":1018},[1016],"match /uploads/{fileName} {\n  allow write: if request.resource.size \u003C 50 * 1024 * 1024\n               && (request.resource.contentType == 'audio/wav'\n                   || request.resource.contentType == 'audio/mpeg');\n  allow read: if true;\n}\n",[895,27618,27616],{"__ignoreMap":728},[842,27620,27621],{},"Deploy with:",[1013,27623,27625],{"className":1080,"code":27624,"language":1082,"meta":728,"style":728},"pnpm build\nfirebase deploy\n",[895,27626,27627,27635],{"__ignoreMap":728},[1086,27628,27629,27632],{"class":1088,"line":1089},[1086,27630,27631],{"class":1092},"pnpm",[1086,27633,27634],{"class":1096}," build\n",[1086,27636,27637,27640],{"class":1088,"line":729},[1086,27638,27639],{"class":1092},"firebase",[1086,27641,27642],{"class":1096}," deploy\n",[4937,27644],{},[863,27646,27648],{"id":27647},"security-considerations","Security Considerations",[842,27650,27651],{},"Even for a PoC, security matters:",[871,27653,27654,27664],{},[874,27655,27656],{},[877,27657,27658,27661],{},[880,27659,27660],{},"Concern",[880,27662,27663],{},"Solution",[887,27665,27666,27683,27693,27703],{},[877,27667,27668,27673],{},[892,27669,27670],{},[996,27671,27672],{},"API key exposure",[892,27674,27675,27676,27679,27680],{},"Stored in ",[895,27677,27678],{},"runtimeConfig"," (server-only), never in ",[895,27681,27682],{},"runtimeConfig.public",[877,27684,27685,27690],{},[892,27686,27687],{},[996,27688,27689],{},"File abuse",[892,27691,27692],{},"Firebase Storage rules: WAV/MP3 only, 50 MB max",[877,27694,27695,27700],{},[892,27696,27697],{},[996,27698,27699],{},"Input validation",[892,27701,27702],{},"Server routes validate URL format, string lengths",[877,27704,27705,27710],{},[892,27706,27707],{},[996,27708,27709],{},"Secrets in git",[892,27711,27712,27714,27715,27718,27719,27722],{},[895,27713,1622],{}," in ",[895,27716,27717],{},".gitignore","; only ",[895,27720,27721],{},".env.example"," committed",[4937,27724],{},[863,27726,27728],{"id":27727},"watch-out-copyright-detection","Watch Out: Copyright Detection",[842,27730,27731,27732,27735],{},"One thing that caught us off guard during testing - Suno has ",[996,27733,27734],{},"built-in copyright detection",". When we tried remixing a well-known track (AC/DC's \"Back in Black\"), the API returned an error:",[1013,27737,27740],{"className":27738,"code":27739,"language":1018},[1016],"Error code: 413\nError message: Uploaded audio matches existing work of art.\n",[895,27741,27739],{"__ignoreMap":728},[842,27743,27744,27745,27748],{},"Suno's system fingerprints uploaded audio and compares it against known copyrighted works. If it detects a match, the remix is rejected with a ",[895,27746,27747],{},"GENERATE_AUDIO_FAILED"," status.",[1901,27750,27751],{},[842,27752,27753,27754,27757],{},"Only upload ",[996,27755,27756],{},"original recordings"," or content you have rights to. Suno will reject copyrighted material, and repeated violations may affect your API account.",[842,27759,27760],{},"This is actually a responsible feature - it prevents the platform from being used to create unauthorized covers or derivatives of copyrighted music. For our PoC, we switched to an original recording and everything worked perfectly.",[4937,27762],{},[863,27764,15096],{"id":15095},[991,27766,27767,27773,27779,27785,27791],{},[961,27768,27769,27772],{},[996,27770,27771],{},"Suno's \"upload-cover\" endpoint is powerful"," - It takes an audio file and re-imagines it in a completely different style while retaining the core melody. The results can be surprisingly good.",[961,27774,27775,27778],{},[996,27776,27777],{},"Copyright detection is real"," - Don't assume you can remix any audio. Suno actively fingerprints uploads and blocks copyrighted material with error code 413.",[961,27780,27781,27784],{},[996,27782,27783],{},"The public URL requirement adds complexity"," - Having to upload to Firebase Storage first adds a step, but it's a clean pattern that keeps the architecture simple.",[961,27786,27787,27790],{},[996,27788,27789],{},"Polling is fine for a PoC"," - While webhooks would be more efficient, the 30-second polling interval works well for a demo. The typical remix takes 1-3 minutes.",[961,27792,27793,27796],{},[996,27794,27795],{},"Nuxt 4 + Nitro is a great combo"," - Server routes give you a built-in API proxy without needing a separate backend service. Perfect for PoCs and small apps.",[4937,27798],{},[863,27800,27802],{"id":27801},"next-steps","Next Steps",[842,27804,27805],{},"This PoC demonstrates the core flow, but there are plenty of ways to extend it:",[958,27807,27808,27814,27820,27826,27832],{},[961,27809,27810,27813],{},[996,27811,27812],{},"Multiple styles per remix"," - Let users generate several style variations at once",[961,27815,27816,27819],{},[996,27817,27818],{},"Audio trimming"," - Add a waveform-based trimmer before sending to Suno",[961,27821,27822,27825],{},[996,27823,27824],{},"User accounts"," - Save remix history with Firebase Auth",[961,27827,27828,27831],{},[996,27829,27830],{},"Batch processing"," - Upload multiple files and queue remixes",[961,27833,27834,27837],{},[996,27835,27836],{},"Stem separation"," - Use Suno's stems endpoint to separate vocals and instruments",[4937,27839],{},[863,27841,27843],{"id":27842},"try-it-yourself","Try It Yourself",[958,27845,27846],{},[961,27847,27848,27851,27852],{},[996,27849,27850],{},"Live demo",": ",[846,27853,27856],{"href":27854,"rel":27855},"https://mtl-suno-poc.web.app",[850],"mtl-suno-poc.web.app",[842,27858,27859,27860,27863],{},"You'll need your own Suno API key from ",[846,27861,26126],{"href":26122,"rel":27862},[850]," and a Firebase project if you want to build something similar.",[4937,27865],{},[863,27867,27869],{"id":27868},"related-articles","Related Articles",[958,27871,27872,27877,27882],{},[961,27873,27874],{},[846,27875,27876],{"href":149},"How to Use Epidemic Sound MCP with Claude",[961,27878,27879],{},[846,27880,27881],{"href":101},"Automatic Song Structure Analysis: How AI Detects Intro, Verse, Chorus",[961,27883,27884],{},[846,27885,27886],{"href":459},"Exporting Ableton Live Locators to JSON with Max for Live",[1680,27888,27889],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":728,"searchDepth":729,"depth":729,"links":27891},[27892,27893,27894,27895,27900,27903,27904,27905,27906,27907,27908,27909],{"id":26096,"depth":729,"text":26097},{"id":26145,"depth":729,"text":26146},{"id":8483,"depth":729,"text":8484},{"id":26347,"depth":729,"text":26348,"children":27896},[27897,27898,27899],{"id":26354,"depth":1112,"text":26355},{"id":26806,"depth":1112,"text":26807},{"id":27161,"depth":1112,"text":27162},{"id":27323,"depth":729,"text":27324,"children":27901},[27902],{"id":27387,"depth":1112,"text":27388},{"id":27448,"depth":729,"text":27449},{"id":27647,"depth":729,"text":27648},{"id":27727,"depth":729,"text":27728},{"id":15095,"depth":729,"text":15096},{"id":27801,"depth":729,"text":27802},{"id":27842,"depth":729,"text":27843},{"id":27868,"depth":729,"text":27869},"2026-02-06T00:00:00.000Z","A step-by-step guide to building a web app that remixes audio using Suno's AI, Nuxt 4, and Firebase Storage.",{"src":27913},"/images/blog/musictechlab_blog_suno-remix-poc.webp",{"enabled":738,"items":27915},[27916,27918,27920,27922],{"text":27917,"icon":5365},"Suno has no official public API; third-party wrappers like sunoapi.org cost about $0.005 per credit.",{"text":27919,"icon":2917},"Firebase Storage bridges the gap between local file uploads and Suno's public URL requirement.",{"text":27921,"icon":7495},"Suno's built-in copyright detection rejects known copyrighted material with error code 413.",{"text":27923,"icon":7498},"Nitro server routes keep the API key server-side so it never reaches the browser.",{},{"title":116,"description":27911},[14100,18784,27927],"audio-analysis","vdlUk3pZwxfUadk0bx3d02X4tJOxLv2oe4g_c_OXEQE",{"id":27930,"title":144,"authors":27931,"badge":723,"body":27935,"category":731,"client":723,"date":30065,"description":30066,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":30067,"keyTakeaways":30069,"meta":30079,"navigation":738,"path":145,"seo":30080,"status":723,"stem":146,"tags":30081,"teaser":723,"__hash__":30082},"posts/blog/music-data/how-to-transcribe-video-to-text-using-whisper.md",[27932],{"name":834,"to":27933,"avatar":27934},"https://www.linkedin.com/in/mariusz-smenzyk",{"src":722},{"type":725,"value":27936,"toc":30033},[27937,27941,27948,27951,27953,27957,27968,27970,27974,27977,27981,27997,28001,28027,28031,28040,28043,28056,28058,28062,28065,28080,28083,28112,28114,28118,28121,28153,28156,28182,28185,28219,28221,28225,28229,28254,28258,28361,28370,28374,28400,28417,28419,28423,28426,28430,28457,28461,28485,28489,28496,28502,28504,28508,28511,29292,29295,29307,29309,29313,29316,29529,29532,29538,29540,29544,29547,29682,29684,29688,29692,29706,29766,29770,29773,29806,29810,29825,29886,29888,29892,29961,29964,29975,29977,29979,29982,30000,30003,30005,30007,30030],[863,27938,27940],{"id":27939},"introduction","Introduction",[842,27942,27943,27944,27947],{},"Got a recorded interview, meeting, or podcast that needs transcription? Instead of paying for online transcription services, you can use ",[996,27945,27946],{},"OpenAI Whisper"," - a free, open-source speech recognition model that runs locally on your machine.",[842,27949,27950],{},"In this guide, we'll walk through the complete pipeline: from video file to ready-to-use text transcription.",[4937,27952],{},[863,27954,27956],{"id":27955},"requirements","Requirements",[958,27958,27959,27962,27965],{},[961,27960,27961],{},"Python 3.8+",[961,27963,27964],{},"FFmpeg (for audio/video conversion)",[961,27966,27967],{},"2-10 GB of free disk space (depending on model size)",[4937,27969],{},[863,27971,27973],{"id":27972},"step-1-install-ffmpeg","Step 1: Install FFmpeg",[842,27975,27976],{},"FFmpeg is needed to extract audio from video files.",[1074,27978,27980],{"id":27979},"macos-homebrew","macOS (Homebrew)",[1013,27982,27984],{"className":1080,"code":27983,"language":1082,"meta":728,"style":728},"brew install ffmpeg\n",[895,27985,27986],{"__ignoreMap":728},[1086,27987,27988,27991,27994],{"class":1088,"line":1089},[1086,27989,27990],{"class":1092},"brew",[1086,27992,27993],{"class":1096}," install",[1086,27995,27996],{"class":1096}," ffmpeg\n",[1074,27998,28000],{"id":27999},"ubuntudebian","Ubuntu/Debian",[1013,28002,28004],{"className":1080,"code":28003,"language":1082,"meta":728,"style":728},"sudo apt update\nsudo apt install ffmpeg\n",[895,28005,28006,28017],{"__ignoreMap":728},[1086,28007,28008,28011,28014],{"class":1088,"line":1089},[1086,28009,28010],{"class":1092},"sudo",[1086,28012,28013],{"class":1096}," apt",[1086,28015,28016],{"class":1096}," update\n",[1086,28018,28019,28021,28023,28025],{"class":1088,"line":729},[1086,28020,28010],{"class":1092},[1086,28022,28013],{"class":1096},[1086,28024,27993],{"class":1096},[1086,28026,27996],{"class":1096},[1074,28028,28030],{"id":28029},"windows","Windows",[842,28032,28033,28034,28039],{},"Download from ",[846,28035,28038],{"href":28036,"rel":28037},"https://ffmpeg.org/download.html",[850],"ffmpeg.org"," and add to PATH.",[842,28041,28042],{},"Verify installation:",[1013,28044,28046],{"className":1080,"code":28045,"language":1082,"meta":728,"style":728},"ffmpeg -version\n",[895,28047,28048],{"__ignoreMap":728},[1086,28049,28050,28053],{"class":1088,"line":1089},[1086,28051,28052],{"class":1092},"ffmpeg",[1086,28054,28055],{"class":1096}," -version\n",[4937,28057],{},[863,28059,28061],{"id":28060},"step-2-install-openai-whisper","Step 2: Install OpenAI Whisper",[842,28063,28064],{},"Install Whisper via pip:",[1013,28066,28068],{"className":1080,"code":28067,"language":1082,"meta":728,"style":728},"pip install openai-whisper\n",[895,28069,28070],{"__ignoreMap":728},[1086,28071,28072,28075,28077],{"class":1088,"line":1089},[1086,28073,28074],{"class":1092},"pip",[1086,28076,27993],{"class":1096},[1086,28078,28079],{"class":1096}," openai-whisper\n",[842,28081,28082],{},"For faster GPU transcription (NVIDIA):",[1013,28084,28086],{"className":1080,"code":28085,"language":1082,"meta":728,"style":728},"pip install openai-whisper torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118\n",[895,28087,28088],{"__ignoreMap":728},[1086,28089,28090,28092,28094,28097,28100,28103,28106,28109],{"class":1088,"line":1089},[1086,28091,28074],{"class":1092},[1086,28093,27993],{"class":1096},[1086,28095,28096],{"class":1096}," openai-whisper",[1086,28098,28099],{"class":1096}," torch",[1086,28101,28102],{"class":1096}," torchvision",[1086,28104,28105],{"class":1096}," torchaudio",[1086,28107,28108],{"class":1096}," --index-url",[1086,28110,28111],{"class":1096}," https://download.pytorch.org/whl/cu118\n",[4937,28113],{},[863,28115,28117],{"id":28116},"step-3-extract-audio-from-video","Step 3: Extract Audio from Video",[842,28119,28120],{},"Pull the audio track from your video file:",[1013,28122,28124],{"className":1080,"code":28123,"language":1082,"meta":728,"style":728},"ffmpeg -i interview.mp4 -vn -acodec libmp3lame -q:a 2 interview.mp3\n",[895,28125,28126],{"__ignoreMap":728},[1086,28127,28128,28130,28133,28136,28139,28142,28145,28148,28150],{"class":1088,"line":1089},[1086,28129,28052],{"class":1092},[1086,28131,28132],{"class":1096}," -i",[1086,28134,28135],{"class":1096}," interview.mp4",[1086,28137,28138],{"class":1096}," -vn",[1086,28140,28141],{"class":1096}," -acodec",[1086,28143,28144],{"class":1096}," libmp3lame",[1086,28146,28147],{"class":1096}," -q:a",[1086,28149,9048],{"class":1187},[1086,28151,28152],{"class":1096}," interview.mp3\n",[842,28154,28155],{},"Flag breakdown:",[958,28157,28158,28164,28170,28176],{},[961,28159,28160,28163],{},[895,28161,28162],{},"-i interview.mp4"," - input file",[961,28165,28166,28169],{},[895,28167,28168],{},"-vn"," - no video (audio only)",[961,28171,28172,28175],{},[895,28173,28174],{},"-acodec libmp3lame"," - MP3 codec",[961,28177,28178,28181],{},[895,28179,28180],{},"-q:a 2"," - high audio quality",[842,28183,28184],{},"For a smaller file (sufficient for transcription):",[1013,28186,28188],{"className":1080,"code":28187,"language":1082,"meta":728,"style":728},"ffmpeg -i interview.mp4 -vn -ar 16000 -ac 1 -b:a 64k interview.mp3\n",[895,28189,28190],{"__ignoreMap":728},[1086,28191,28192,28194,28196,28198,28200,28203,28206,28209,28211,28214,28217],{"class":1088,"line":1089},[1086,28193,28052],{"class":1092},[1086,28195,28132],{"class":1096},[1086,28197,28135],{"class":1096},[1086,28199,28138],{"class":1096},[1086,28201,28202],{"class":1096}," -ar",[1086,28204,28205],{"class":1187}," 16000",[1086,28207,28208],{"class":1096}," -ac",[1086,28210,23488],{"class":1187},[1086,28212,28213],{"class":1096}," -b:a",[1086,28215,28216],{"class":1096}," 64k",[1086,28218,28152],{"class":1096},[4937,28220],{},[863,28222,28224],{"id":28223},"step-4-transcribe-with-whisper","Step 4: Transcribe with Whisper",[1074,28226,28228],{"id":28227},"basic-cli-usage","Basic CLI Usage",[1013,28230,28232],{"className":1080,"code":28231,"language":1082,"meta":728,"style":728},"whisper interview.mp3 --language English --model medium\n",[895,28233,28234],{"__ignoreMap":728},[1086,28235,28236,28239,28242,28245,28248,28251],{"class":1088,"line":1089},[1086,28237,28238],{"class":1092},"whisper",[1086,28240,28241],{"class":1096}," interview.mp3",[1086,28243,28244],{"class":1096}," --language",[1086,28246,28247],{"class":1096}," English",[1086,28249,28250],{"class":1096}," --model",[1086,28252,28253],{"class":1096}," medium\n",[1074,28255,28257],{"id":28256},"available-models","Available Models",[871,28259,28260,28278],{},[874,28261,28262],{},[877,28263,28264,28266,28269,28272,28275],{},[880,28265,11370],{},[880,28267,28268],{},"Size",[880,28270,28271],{},"VRAM",[880,28273,28274],{},"Quality",[880,28276,28277],{},"Speed",[887,28279,28280,28296,28311,28327,28344],{},[877,28281,28282,28285,28288,28291,28293],{},[892,28283,28284],{},"tiny",[892,28286,28287],{},"39 MB",[892,28289,28290],{},"~1 GB",[892,28292,2123],{},[892,28294,28295],{},"Very fast",[877,28297,28298,28301,28304,28306,28308],{},[892,28299,28300],{},"base",[892,28302,28303],{},"74 MB",[892,28305,28290],{},[892,28307,2126],{},[892,28309,28310],{},"Fast",[877,28312,28313,28316,28319,28322,28325],{},[892,28314,28315],{},"small",[892,28317,28318],{},"244 MB",[892,28320,28321],{},"~2 GB",[892,28323,28324],{},"Good",[892,28326,2126],{},[877,28328,28329,28332,28335,28338,28341],{},[892,28330,28331],{},"medium",[892,28333,28334],{},"769 MB",[892,28336,28337],{},"~5 GB",[892,28339,28340],{},"Very good",[892,28342,28343],{},"Slow",[877,28345,28346,28349,28352,28355,28358],{},[892,28347,28348],{},"large",[892,28350,28351],{},"1550 MB",[892,28353,28354],{},"~10 GB",[892,28356,28357],{},"Best",[892,28359,28360],{},"Very slow",[842,28362,28363,28364,28366,28367,28369],{},"For most use cases, ",[895,28365,28315],{}," or ",[895,28368,28331],{}," offers the best quality-to-speed ratio.",[1074,28371,28373],{"id":28372},"save-to-text-file","Save to Text File",[1013,28375,28377],{"className":1080,"code":28376,"language":1082,"meta":728,"style":728},"whisper interview.mp3 --language English --model medium --output_format txt\n",[895,28378,28379],{"__ignoreMap":728},[1086,28380,28381,28383,28385,28387,28389,28391,28394,28397],{"class":1088,"line":1089},[1086,28382,28238],{"class":1092},[1086,28384,28241],{"class":1096},[1086,28386,28244],{"class":1096},[1086,28388,28247],{"class":1096},[1086,28390,28250],{"class":1096},[1086,28392,28393],{"class":1096}," medium",[1086,28395,28396],{"class":1096}," --output_format",[1086,28398,28399],{"class":1096}," txt\n",[842,28401,28402,28403,5660,28406,5660,28409,5660,28412,5660,28415],{},"Available output formats: ",[895,28404,28405],{},"txt",[895,28407,28408],{},"vtt",[895,28410,28411],{},"srt",[895,28413,28414],{},"tsv",[895,28416,1139],{},[4937,28418],{},[863,28420,28422],{"id":28421},"example-transcribing-a-user-interview","Example: Transcribing a User Interview",[842,28424,28425],{},"Let's say you recorded a 15-minute user feedback session about your product. Here's the complete workflow:",[1074,28427,28429],{"id":28428},"_1-extract-audio","1. Extract Audio",[1013,28431,28433],{"className":1080,"code":28432,"language":1082,"meta":728,"style":728},"ffmpeg -i user_feedback_session.mov -vn -ar 16000 -ac 1 feedback.mp3\n",[895,28434,28435],{"__ignoreMap":728},[1086,28436,28437,28439,28441,28444,28446,28448,28450,28452,28454],{"class":1088,"line":1089},[1086,28438,28052],{"class":1092},[1086,28440,28132],{"class":1096},[1086,28442,28443],{"class":1096}," user_feedback_session.mov",[1086,28445,28138],{"class":1096},[1086,28447,28202],{"class":1096},[1086,28449,28205],{"class":1187},[1086,28451,28208],{"class":1096},[1086,28453,23488],{"class":1187},[1086,28455,28456],{"class":1096}," feedback.mp3\n",[1074,28458,28460],{"id":28459},"_2-run-transcription","2. Run Transcription",[1013,28462,28464],{"className":1080,"code":28463,"language":1082,"meta":728,"style":728},"whisper feedback.mp3 --language English --model medium --output_format txt\n",[895,28465,28466],{"__ignoreMap":728},[1086,28467,28468,28470,28473,28475,28477,28479,28481,28483],{"class":1088,"line":1089},[1086,28469,28238],{"class":1092},[1086,28471,28472],{"class":1096}," feedback.mp3",[1086,28474,28244],{"class":1096},[1086,28476,28247],{"class":1096},[1086,28478,28250],{"class":1096},[1086,28480,28393],{"class":1096},[1086,28482,28396],{"class":1096},[1086,28484,28399],{"class":1096},[1074,28486,28488],{"id":28487},"_3-output","3. Output",[842,28490,28491,28492,28495],{},"Whisper creates ",[895,28493,28494],{},"feedback.txt"," with content like:",[1013,28497,28500],{"className":28498,"code":28499,"language":1018},[1016],"So the first thing about these signals before the interval.\nYes, so you set a 25-second interval and before the interval\nyou get 4 audio signals, let's say, the main signal with an\naccent and then 4 weaker ones again. So basically if you don't\narrive exactly on the signal, you know that you need to speed\nup by two seconds or slow down by two seconds because the sound\ntells you that.\n",[895,28501,28499],{"__ignoreMap":728},[4937,28503],{},[863,28505,28507],{"id":28506},"example-python-script-for-batch-processing","Example: Python Script for Batch Processing",[842,28509,28510],{},"For processing multiple files or integrating into your workflow:",[1013,28512,28514],{"className":1368,"code":28513,"language":1250,"meta":728,"style":728},"#!/usr/bin/env python3\n\"\"\"\nVideo to Text Transcription Pipeline\nUses FFmpeg + OpenAI Whisper\n\"\"\"\n\nimport subprocess\nimport whisper\nfrom pathlib import Path\n\n\ndef extract_audio(video_path: str, audio_path: str) -> None:\n    \"\"\"Extract audio from video using FFmpeg.\"\"\"\n    cmd = [\n        \"ffmpeg\", \"-i\", video_path,\n        \"-vn\", \"-ar\", \"16000\", \"-ac\", \"1\",\n        \"-b:a\", \"64k\", \"-y\",\n        audio_path\n    ]\n    subprocess.run(cmd, check=True, capture_output=True)\n    print(f\"Audio extracted: {audio_path}\")\n\n\ndef transcribe_audio(\n    audio_path: str,\n    output_path: str,\n    model_name: str = \"medium\",\n    language: str = \"en\"\n) -> str:\n    \"\"\"Transcribe audio using Whisper.\"\"\"\n    print(f\"Loading model {model_name}...\")\n    model = whisper.load_model(model_name)\n\n    print(\"Transcribing...\")\n    result = model.transcribe(audio_path, language=language)\n\n    text = result[\"text\"]\n\n    with open(output_path, \"w\", encoding=\"utf-8\") as f:\n        f.write(text)\n\n    print(f\"Transcription saved: {output_path}\")\n    return text\n\n\ndef main():\n    # Configuration\n    video_file = \"meeting_recording.mp4\"\n    audio_file = \"temp_audio.mp3\"\n    output_file = \"meeting_transcript.txt\"\n\n    # Pipeline\n    extract_audio(video_file, audio_file)\n    text = transcribe_audio(audio_file, output_file)\n\n    # Cleanup temp file\n    Path(audio_file).unlink()\n\n    print(f\"\\nTranscription preview:\\n{text[:500]}...\")\n\n\nif __name__ == \"__main__\":\n    main()\n",[895,28515,28516,28521,28525,28530,28535,28539,28543,28550,28557,28569,28573,28577,28608,28617,28626,28650,28695,28724,28729,28733,28759,28781,28785,28789,28798,28809,28820,28839,28857,28867,28876,28899,28920,28924,28939,28969,28973,28992,28996,29038,29053,29057,29078,29085,29089,29093,29102,29107,29121,29135,29149,29153,29158,29175,29195,29199,29204,29220,29224,29259,29263,29267,29285],{"__ignoreMap":728},[1086,28517,28518],{"class":1088,"line":1089},[1086,28519,28520],{"class":1427},"#!/usr/bin/env python3\n",[1086,28522,28523],{"class":1088,"line":729},[1086,28524,1431],{"class":1423},[1086,28526,28527],{"class":1088,"line":1112},[1086,28528,28529],{"class":1427},"Video to Text Transcription Pipeline\n",[1086,28531,28532],{"class":1088,"line":1181},[1086,28533,28534],{"class":1427},"Uses FFmpeg + OpenAI Whisper\n",[1086,28536,28537],{"class":1088,"line":1205},[1086,28538,1431],{"class":1423},[1086,28540,28541],{"class":1088,"line":1276},[1086,28542,3390],{"emptyLinePlaceholder":738},[1086,28544,28545,28547],{"class":1088,"line":1282},[1086,28546,6503],{"class":1423},[1086,28548,28549],{"class":1436}," subprocess\n",[1086,28551,28552,28554],{"class":1088,"line":1288},[1086,28553,6503],{"class":1423},[1086,28555,28556],{"class":1436}," whisper\n",[1086,28558,28559,28561,28564,28566],{"class":1088,"line":2685},[1086,28560,15570],{"class":1423},[1086,28562,28563],{"class":1436}," pathlib ",[1086,28565,6503],{"class":1423},[1086,28567,28568],{"class":1436}," Path\n",[1086,28570,28571],{"class":1088,"line":2700},[1086,28572,3390],{"emptyLinePlaceholder":738},[1086,28574,28575],{"class":1088,"line":3398},[1086,28576,3390],{"emptyLinePlaceholder":738},[1086,28578,28579,28581,28584,28586,28589,28591,28593,28595,28598,28600,28602,28604,28606],{"class":1088,"line":1715},[1086,28580,1392],{"class":1155},[1086,28582,28583],{"class":1105}," extract_audio",[1086,28585,1398],{"class":1146},[1086,28587,28588],{"class":1401},"video_path",[1086,28590,1133],{"class":1146},[1086,28592,1407],{"class":1092},[1086,28594,1227],{"class":1146},[1086,28596,28597],{"class":1401}," audio_path",[1086,28599,1133],{"class":1146},[1086,28601,1407],{"class":1092},[1086,28603,1410],{"class":1146},[1086,28605,1413],{"class":1146},[1086,28607,19794],{"class":1146},[1086,28609,28610,28612,28615],{"class":1088,"line":3409},[1086,28611,1424],{"class":1423},[1086,28613,28614],{"class":1427},"Extract audio from video using FFmpeg.",[1086,28616,1431],{"class":1423},[1086,28618,28619,28622,28624],{"class":1088,"line":3415},[1086,28620,28621],{"class":1436},"    cmd ",[1086,28623,1440],{"class":1146},[1086,28625,6580],{"class":1146},[1086,28627,28628,28630,28632,28634,28636,28638,28641,28643,28645,28648],{"class":1088,"line":3421},[1086,28629,6046],{"class":1146},[1086,28631,28052],{"class":1096},[1086,28633,1159],{"class":1146},[1086,28635,1227],{"class":1146},[1086,28637,1195],{"class":1146},[1086,28639,28640],{"class":1096},"-i",[1086,28642,1159],{"class":1146},[1086,28644,1227],{"class":1146},[1086,28646,28647],{"class":1436}," video_path",[1086,28649,1202],{"class":1146},[1086,28651,28652,28654,28656,28658,28660,28662,28665,28667,28669,28671,28674,28676,28678,28680,28683,28685,28687,28689,28691,28693],{"class":1088,"line":3427},[1086,28653,6046],{"class":1146},[1086,28655,28168],{"class":1096},[1086,28657,1159],{"class":1146},[1086,28659,1227],{"class":1146},[1086,28661,1195],{"class":1146},[1086,28663,28664],{"class":1096},"-ar",[1086,28666,1159],{"class":1146},[1086,28668,1227],{"class":1146},[1086,28670,1195],{"class":1146},[1086,28672,28673],{"class":1096},"16000",[1086,28675,1159],{"class":1146},[1086,28677,1227],{"class":1146},[1086,28679,1195],{"class":1146},[1086,28681,28682],{"class":1096},"-ac",[1086,28684,1159],{"class":1146},[1086,28686,1227],{"class":1146},[1086,28688,1195],{"class":1146},[1086,28690,4434],{"class":1096},[1086,28692,1159],{"class":1146},[1086,28694,1202],{"class":1146},[1086,28696,28697,28699,28702,28704,28706,28708,28711,28713,28715,28717,28720,28722],{"class":1088,"line":3433},[1086,28698,6046],{"class":1146},[1086,28700,28701],{"class":1096},"-b:a",[1086,28703,1159],{"class":1146},[1086,28705,1227],{"class":1146},[1086,28707,1195],{"class":1146},[1086,28709,28710],{"class":1096},"64k",[1086,28712,1159],{"class":1146},[1086,28714,1227],{"class":1146},[1086,28716,1195],{"class":1146},[1086,28718,28719],{"class":1096},"-y",[1086,28721,1159],{"class":1146},[1086,28723,1202],{"class":1146},[1086,28725,28726],{"class":1088,"line":3439},[1086,28727,28728],{"class":1436},"        audio_path\n",[1086,28730,28731],{"class":1088,"line":3444},[1086,28732,27566],{"class":1146},[1086,28734,28735,28738,28740,28742,28744,28747,28749,28752,28754,28757],{"class":1088,"line":3450},[1086,28736,28737],{"class":1436},"    subprocess",[1086,28739,861],{"class":1146},[1086,28741,1241],{"class":1105},[1086,28743,1398],{"class":1146},[1086,28745,28746],{"class":1105},"cmd",[1086,28748,1227],{"class":1146},[1086,28750,28751],{"class":1401}," check",[1086,28753,22246],{"class":1146},[1086,28755,28756],{"class":1401}," capture_output",[1086,28758,22252],{"class":1146},[1086,28760,28761,28763,28765,28767,28770,28772,28775,28777,28779],{"class":1088,"line":3456},[1086,28762,11068],{"class":1105},[1086,28764,1398],{"class":1146},[1086,28766,5962],{"class":1155},[1086,28768,28769],{"class":1096},"\"Audio extracted: ",[1086,28771,4409],{"class":1187},[1086,28773,28774],{"class":1105},"audio_path",[1086,28776,4423],{"class":1187},[1086,28778,1159],{"class":1096},[1086,28780,1455],{"class":1146},[1086,28782,28783],{"class":1088,"line":3462},[1086,28784,3390],{"emptyLinePlaceholder":738},[1086,28786,28787],{"class":1088,"line":3467},[1086,28788,3390],{"emptyLinePlaceholder":738},[1086,28790,28791,28793,28796],{"class":1088,"line":3473},[1086,28792,1392],{"class":1155},[1086,28794,28795],{"class":1105}," transcribe_audio",[1086,28797,4094],{"class":1146},[1086,28799,28800,28803,28805,28807],{"class":1088,"line":3479},[1086,28801,28802],{"class":1401},"    audio_path",[1086,28804,1133],{"class":1146},[1086,28806,1407],{"class":1092},[1086,28808,1202],{"class":1146},[1086,28810,28811,28814,28816,28818],{"class":1088,"line":3485},[1086,28812,28813],{"class":1401},"    output_path",[1086,28815,1133],{"class":1146},[1086,28817,1407],{"class":1092},[1086,28819,1202],{"class":1146},[1086,28821,28822,28825,28827,28829,28831,28833,28835,28837],{"class":1088,"line":3491},[1086,28823,28824],{"class":1401},"    model_name",[1086,28826,1133],{"class":1146},[1086,28828,1407],{"class":1092},[1086,28830,19552],{"class":1146},[1086,28832,1195],{"class":1146},[1086,28834,28331],{"class":1096},[1086,28836,1159],{"class":1146},[1086,28838,1202],{"class":1146},[1086,28840,28841,28844,28846,28848,28850,28852,28855],{"class":1088,"line":3497},[1086,28842,28843],{"class":1401},"    language",[1086,28845,1133],{"class":1146},[1086,28847,1407],{"class":1092},[1086,28849,19552],{"class":1146},[1086,28851,1195],{"class":1146},[1086,28853,28854],{"class":1096},"en",[1086,28856,4441],{"class":1146},[1086,28858,28859,28861,28863,28865],{"class":1088,"line":3503},[1086,28860,1410],{"class":1146},[1086,28862,1413],{"class":1146},[1086,28864,1407],{"class":1092},[1086,28866,1418],{"class":1146},[1086,28868,28869,28871,28874],{"class":1088,"line":3509},[1086,28870,1424],{"class":1423},[1086,28872,28873],{"class":1427},"Transcribe audio using Whisper.",[1086,28875,1431],{"class":1423},[1086,28877,28878,28880,28882,28884,28887,28889,28892,28894,28897],{"class":1088,"line":3515},[1086,28879,11068],{"class":1105},[1086,28881,1398],{"class":1146},[1086,28883,5962],{"class":1155},[1086,28885,28886],{"class":1096},"\"Loading model ",[1086,28888,4409],{"class":1187},[1086,28890,28891],{"class":1105},"model_name",[1086,28893,4423],{"class":1187},[1086,28895,28896],{"class":1096},"...\"",[1086,28898,1455],{"class":1146},[1086,28900,28901,28904,28906,28909,28911,28914,28916,28918],{"class":1088,"line":3520},[1086,28902,28903],{"class":1436},"    model ",[1086,28905,1440],{"class":1146},[1086,28907,28908],{"class":1436}," whisper",[1086,28910,861],{"class":1146},[1086,28912,28913],{"class":1105},"load_model",[1086,28915,1398],{"class":1146},[1086,28917,28891],{"class":1105},[1086,28919,1455],{"class":1146},[1086,28921,28922],{"class":1088,"line":3526},[1086,28923,3390],{"emptyLinePlaceholder":738},[1086,28925,28926,28928,28930,28932,28935,28937],{"class":1088,"line":3531},[1086,28927,11068],{"class":1105},[1086,28929,1398],{"class":1146},[1086,28931,1159],{"class":1146},[1086,28933,28934],{"class":1096},"Transcribing...",[1086,28936,1159],{"class":1146},[1086,28938,1455],{"class":1146},[1086,28940,28941,28943,28945,28948,28950,28953,28955,28957,28959,28962,28964,28967],{"class":1088,"line":3537},[1086,28942,1437],{"class":1436},[1086,28944,1440],{"class":1146},[1086,28946,28947],{"class":1436}," model",[1086,28949,861],{"class":1146},[1086,28951,28952],{"class":1105},"transcribe",[1086,28954,1398],{"class":1146},[1086,28956,28774],{"class":1105},[1086,28958,1227],{"class":1146},[1086,28960,28961],{"class":1401}," language",[1086,28963,1440],{"class":1146},[1086,28965,28966],{"class":1105},"language",[1086,28968,1455],{"class":1146},[1086,28970,28971],{"class":1088,"line":3543},[1086,28972,3390],{"emptyLinePlaceholder":738},[1086,28974,28975,28978,28980,28982,28984,28986,28988,28990],{"class":1088,"line":3549},[1086,28976,28977],{"class":1436},"    text ",[1086,28979,1440],{"class":1146},[1086,28981,23102],{"class":1436},[1086,28983,4340],{"class":1146},[1086,28985,1159],{"class":1146},[1086,28987,1018],{"class":1096},[1086,28989,1159],{"class":1146},[1086,28991,1273],{"class":1146},[1086,28993,28994],{"class":1088,"line":3555},[1086,28995,3390],{"emptyLinePlaceholder":738},[1086,28997,28998,29000,29003,29005,29008,29010,29012,29015,29017,29019,29022,29024,29026,29028,29030,29032,29034,29036],{"class":1088,"line":3561},[1086,28999,20368],{"class":1423},[1086,29001,29002],{"class":1105}," open",[1086,29004,1398],{"class":1146},[1086,29006,29007],{"class":1105},"output_path",[1086,29009,1227],{"class":1146},[1086,29011,1195],{"class":1146},[1086,29013,29014],{"class":1096},"w",[1086,29016,1159],{"class":1146},[1086,29018,1227],{"class":1146},[1086,29020,29021],{"class":1401}," encoding",[1086,29023,1440],{"class":1146},[1086,29025,1159],{"class":1146},[1086,29027,24171],{"class":1096},[1086,29029,1159],{"class":1146},[1086,29031,1410],{"class":1146},[1086,29033,19873],{"class":1423},[1086,29035,4403],{"class":1436},[1086,29037,1418],{"class":1146},[1086,29039,29040,29042,29044,29047,29049,29051],{"class":1088,"line":3567},[1086,29041,6058],{"class":1436},[1086,29043,861],{"class":1146},[1086,29045,29046],{"class":1105},"write",[1086,29048,1398],{"class":1146},[1086,29050,1018],{"class":1105},[1086,29052,1455],{"class":1146},[1086,29054,29055],{"class":1088,"line":17749},[1086,29056,3390],{"emptyLinePlaceholder":738},[1086,29058,29059,29061,29063,29065,29068,29070,29072,29074,29076],{"class":1088,"line":19843},[1086,29060,11068],{"class":1105},[1086,29062,1398],{"class":1146},[1086,29064,5962],{"class":1155},[1086,29066,29067],{"class":1096},"\"Transcription saved: ",[1086,29069,4409],{"class":1187},[1086,29071,29007],{"class":1105},[1086,29073,4423],{"class":1187},[1086,29075,1159],{"class":1096},[1086,29077,1455],{"class":1146},[1086,29079,29080,29082],{"class":1088,"line":19848},[1086,29081,1460],{"class":1423},[1086,29083,29084],{"class":1436}," text\n",[1086,29086,29087],{"class":1088,"line":19880},[1086,29088,3390],{"emptyLinePlaceholder":738},[1086,29090,29091],{"class":1088,"line":19896},[1086,29092,3390],{"emptyLinePlaceholder":738},[1086,29094,29095,29097,29100],{"class":1088,"line":19919},[1086,29096,1392],{"class":1155},[1086,29098,29099],{"class":1105}," main",[1086,29101,5947],{"class":1146},[1086,29103,29104],{"class":1088,"line":19927},[1086,29105,29106],{"class":1427},"    # Configuration\n",[1086,29108,29109,29112,29114,29116,29119],{"class":1088,"line":19948},[1086,29110,29111],{"class":1436},"    video_file ",[1086,29113,1440],{"class":1146},[1086,29115,1195],{"class":1146},[1086,29117,29118],{"class":1096},"meeting_recording.mp4",[1086,29120,4441],{"class":1146},[1086,29122,29123,29126,29128,29130,29133],{"class":1088,"line":19968},[1086,29124,29125],{"class":1436},"    audio_file ",[1086,29127,1440],{"class":1146},[1086,29129,1195],{"class":1146},[1086,29131,29132],{"class":1096},"temp_audio.mp3",[1086,29134,4441],{"class":1146},[1086,29136,29137,29140,29142,29144,29147],{"class":1088,"line":19987},[1086,29138,29139],{"class":1436},"    output_file ",[1086,29141,1440],{"class":1146},[1086,29143,1195],{"class":1146},[1086,29145,29146],{"class":1096},"meeting_transcript.txt",[1086,29148,4441],{"class":1146},[1086,29150,29151],{"class":1088,"line":20007},[1086,29152,3390],{"emptyLinePlaceholder":738},[1086,29154,29155],{"class":1088,"line":20013},[1086,29156,29157],{"class":1427},"    # Pipeline\n",[1086,29159,29160,29163,29165,29168,29170,29173],{"class":1088,"line":20021},[1086,29161,29162],{"class":1105},"    extract_audio",[1086,29164,1398],{"class":1146},[1086,29166,29167],{"class":1105},"video_file",[1086,29169,1227],{"class":1146},[1086,29171,29172],{"class":1105}," audio_file",[1086,29174,1455],{"class":1146},[1086,29176,29177,29179,29181,29183,29185,29188,29190,29193],{"class":1088,"line":20051},[1086,29178,28977],{"class":1436},[1086,29180,1440],{"class":1146},[1086,29182,28795],{"class":1105},[1086,29184,1398],{"class":1146},[1086,29186,29187],{"class":1105},"audio_file",[1086,29189,1227],{"class":1146},[1086,29191,29192],{"class":1105}," output_file",[1086,29194,1455],{"class":1146},[1086,29196,29197],{"class":1088,"line":20072},[1086,29198,3390],{"emptyLinePlaceholder":738},[1086,29200,29201],{"class":1088,"line":20077},[1086,29202,29203],{"class":1427},"    # Cleanup temp file\n",[1086,29205,29206,29209,29211,29213,29215,29218],{"class":1088,"line":20083},[1086,29207,29208],{"class":1105},"    Path",[1086,29210,1398],{"class":1146},[1086,29212,29187],{"class":1105},[1086,29214,6786],{"class":1146},[1086,29216,29217],{"class":1105},"unlink",[1086,29219,1387],{"class":1146},[1086,29221,29222],{"class":1088,"line":20095},[1086,29223,3390],{"emptyLinePlaceholder":738},[1086,29225,29226,29228,29230,29232,29234,29236,29239,29241,29243,29245,29248,29251,29253,29255,29257],{"class":1088,"line":20112},[1086,29227,11068],{"class":1105},[1086,29229,1398],{"class":1146},[1086,29231,5962],{"class":1155},[1086,29233,1159],{"class":1096},[1086,29235,6016],{"class":1436},[1086,29237,29238],{"class":1096},"Transcription preview:",[1086,29240,6016],{"class":1436},[1086,29242,4409],{"class":1187},[1086,29244,1018],{"class":1105},[1086,29246,29247],{"class":1146},"[:",[1086,29249,29250],{"class":1187},"500",[1086,29252,4420],{"class":1146},[1086,29254,4423],{"class":1187},[1086,29256,28896],{"class":1096},[1086,29258,1455],{"class":1146},[1086,29260,29261],{"class":1088,"line":20117},[1086,29262,3390],{"emptyLinePlaceholder":738},[1086,29264,29265],{"class":1088,"line":20139},[1086,29266,3390],{"emptyLinePlaceholder":738},[1086,29268,29269,29271,29274,29276,29278,29281,29283],{"class":1088,"line":20169},[1086,29270,11056],{"class":1423},[1086,29272,29273],{"class":1436}," __name__ ",[1086,29275,6480],{"class":1146},[1086,29277,1195],{"class":1146},[1086,29279,29280],{"class":1096},"__main__",[1086,29282,1159],{"class":1146},[1086,29284,1418],{"class":1146},[1086,29286,29287,29290],{"class":1088,"line":20197},[1086,29288,29289],{"class":1105},"    main",[1086,29291,1387],{"class":1146},[842,29293,29294],{},"Run it:",[1013,29296,29298],{"className":1080,"code":29297,"language":1082,"meta":728,"style":728},"python transcribe.py\n",[895,29299,29300],{"__ignoreMap":728},[1086,29301,29302,29304],{"class":1088,"line":1089},[1086,29303,1250],{"class":1092},[1086,29305,29306],{"class":1096}," transcribe.py\n",[4937,29308],{},[863,29310,29312],{"id":29311},"example-transcription-with-timestamps","Example: Transcription with Timestamps",[842,29314,29315],{},"Need timestamps for subtitles or reference? Use the segments feature:",[1013,29317,29319],{"className":1368,"code":29318,"language":1250,"meta":728,"style":728},"import whisper\n\nmodel = whisper.load_model(\"medium\")\nresult = model.transcribe(\"podcast_episode.mp3\", language=\"en\")\n\n# Print segments with timestamps\nfor segment in result[\"segments\"]:\n    start = segment[\"start\"]\n    end = segment[\"end\"]\n    text = segment[\"text\"].strip()\n    print(f\"[{start:.1f}s - {end:.1f}s] {text}\")\n",[895,29320,29321,29327,29331,29354,29390,29394,29399,29421,29442,29462,29485],{"__ignoreMap":728},[1086,29322,29323,29325],{"class":1088,"line":1089},[1086,29324,6503],{"class":1423},[1086,29326,28556],{"class":1436},[1086,29328,29329],{"class":1088,"line":729},[1086,29330,3390],{"emptyLinePlaceholder":738},[1086,29332,29333,29336,29338,29340,29342,29344,29346,29348,29350,29352],{"class":1088,"line":1112},[1086,29334,29335],{"class":1436},"model ",[1086,29337,1440],{"class":1146},[1086,29339,28908],{"class":1436},[1086,29341,861],{"class":1146},[1086,29343,28913],{"class":1105},[1086,29345,1398],{"class":1146},[1086,29347,1159],{"class":1146},[1086,29349,28331],{"class":1096},[1086,29351,1159],{"class":1146},[1086,29353,1455],{"class":1146},[1086,29355,29356,29359,29361,29363,29365,29367,29369,29371,29374,29376,29378,29380,29382,29384,29386,29388],{"class":1088,"line":1181},[1086,29357,29358],{"class":1436},"result ",[1086,29360,1440],{"class":1146},[1086,29362,28947],{"class":1436},[1086,29364,861],{"class":1146},[1086,29366,28952],{"class":1105},[1086,29368,1398],{"class":1146},[1086,29370,1159],{"class":1146},[1086,29372,29373],{"class":1096},"podcast_episode.mp3",[1086,29375,1159],{"class":1146},[1086,29377,1227],{"class":1146},[1086,29379,28961],{"class":1401},[1086,29381,1440],{"class":1146},[1086,29383,1159],{"class":1146},[1086,29385,28854],{"class":1096},[1086,29387,1159],{"class":1146},[1086,29389,1455],{"class":1146},[1086,29391,29392],{"class":1088,"line":1205},[1086,29393,3390],{"emptyLinePlaceholder":738},[1086,29395,29396],{"class":1088,"line":1276},[1086,29397,29398],{"class":1427},"# Print segments with timestamps\n",[1086,29400,29401,29403,29406,29408,29410,29412,29414,29417,29419],{"class":1088,"line":1282},[1086,29402,10799],{"class":1423},[1086,29404,29405],{"class":1436}," segment ",[1086,29407,5931],{"class":1423},[1086,29409,23102],{"class":1436},[1086,29411,4340],{"class":1146},[1086,29413,1159],{"class":1146},[1086,29415,29416],{"class":1096},"segments",[1086,29418,1159],{"class":1146},[1086,29420,10888],{"class":1146},[1086,29422,29423,29426,29428,29431,29433,29435,29438,29440],{"class":1088,"line":1288},[1086,29424,29425],{"class":1436},"    start ",[1086,29427,1440],{"class":1146},[1086,29429,29430],{"class":1436}," segment",[1086,29432,4340],{"class":1146},[1086,29434,1159],{"class":1146},[1086,29436,29437],{"class":1096},"start",[1086,29439,1159],{"class":1146},[1086,29441,1273],{"class":1146},[1086,29443,29444,29447,29449,29451,29453,29455,29458,29460],{"class":1088,"line":2685},[1086,29445,29446],{"class":1436},"    end ",[1086,29448,1440],{"class":1146},[1086,29450,29430],{"class":1436},[1086,29452,4340],{"class":1146},[1086,29454,1159],{"class":1146},[1086,29456,29457],{"class":1096},"end",[1086,29459,1159],{"class":1146},[1086,29461,1273],{"class":1146},[1086,29463,29464,29466,29468,29470,29472,29474,29476,29478,29481,29483],{"class":1088,"line":2700},[1086,29465,28977],{"class":1436},[1086,29467,1440],{"class":1146},[1086,29469,29430],{"class":1436},[1086,29471,4340],{"class":1146},[1086,29473,1159],{"class":1146},[1086,29475,1018],{"class":1096},[1086,29477,1159],{"class":1146},[1086,29479,29480],{"class":1146},"].",[1086,29482,6789],{"class":1105},[1086,29484,1387],{"class":1146},[1086,29486,29487,29489,29491,29493,29496,29498,29500,29503,29505,29508,29510,29512,29514,29516,29519,29521,29523,29525,29527],{"class":1088,"line":3398},[1086,29488,11068],{"class":1105},[1086,29490,1398],{"class":1146},[1086,29492,5962],{"class":1155},[1086,29494,29495],{"class":1096},"\"[",[1086,29497,4409],{"class":1187},[1086,29499,29437],{"class":1105},[1086,29501,29502],{"class":1155},":.1f",[1086,29504,4423],{"class":1187},[1086,29506,29507],{"class":1096},"s - ",[1086,29509,4409],{"class":1187},[1086,29511,29457],{"class":1105},[1086,29513,29502],{"class":1155},[1086,29515,4423],{"class":1187},[1086,29517,29518],{"class":1096},"s] ",[1086,29520,4409],{"class":1187},[1086,29522,1018],{"class":1105},[1086,29524,4423],{"class":1187},[1086,29526,1159],{"class":1096},[1086,29528,1455],{"class":1146},[842,29530,29531],{},"Output:",[1013,29533,29536],{"className":29534,"code":29535,"language":1018},[1016],"[0.0s - 4.2s] Welcome to the show. Today we're talking about...\n[4.2s - 8.7s] ...building hardware products for athletes.\n[8.7s - 15.3s] Our guest has been working on a tempo trainer device.\n",[895,29537,29535],{"__ignoreMap":728},[4937,29539],{},[863,29541,29543],{"id":29542},"example-multi-language-detection","Example: Multi-Language Detection",[842,29545,29546],{},"Don't know the language? Let Whisper detect it:",[1013,29548,29550],{"className":1368,"code":29549,"language":1250,"meta":728,"style":728},"import whisper\n\nmodel = whisper.load_model(\"medium\")\n\n# Auto-detect language\nresult = model.transcribe(\"unknown_language.mp3\")\n\nprint(f\"Detected language: {result['language']}\")\nprint(f\"Text: {result['text']}\")\n",[895,29551,29552,29558,29562,29584,29588,29593,29616,29620,29651],{"__ignoreMap":728},[1086,29553,29554,29556],{"class":1088,"line":1089},[1086,29555,6503],{"class":1423},[1086,29557,28556],{"class":1436},[1086,29559,29560],{"class":1088,"line":729},[1086,29561,3390],{"emptyLinePlaceholder":738},[1086,29563,29564,29566,29568,29570,29572,29574,29576,29578,29580,29582],{"class":1088,"line":1112},[1086,29565,29335],{"class":1436},[1086,29567,1440],{"class":1146},[1086,29569,28908],{"class":1436},[1086,29571,861],{"class":1146},[1086,29573,28913],{"class":1105},[1086,29575,1398],{"class":1146},[1086,29577,1159],{"class":1146},[1086,29579,28331],{"class":1096},[1086,29581,1159],{"class":1146},[1086,29583,1455],{"class":1146},[1086,29585,29586],{"class":1088,"line":1181},[1086,29587,3390],{"emptyLinePlaceholder":738},[1086,29589,29590],{"class":1088,"line":1205},[1086,29591,29592],{"class":1427},"# Auto-detect language\n",[1086,29594,29595,29597,29599,29601,29603,29605,29607,29609,29612,29614],{"class":1088,"line":1276},[1086,29596,29358],{"class":1436},[1086,29598,1440],{"class":1146},[1086,29600,28947],{"class":1436},[1086,29602,861],{"class":1146},[1086,29604,28952],{"class":1105},[1086,29606,1398],{"class":1146},[1086,29608,1159],{"class":1146},[1086,29610,29611],{"class":1096},"unknown_language.mp3",[1086,29613,1159],{"class":1146},[1086,29615,1455],{"class":1146},[1086,29617,29618],{"class":1088,"line":1282},[1086,29619,3390],{"emptyLinePlaceholder":738},[1086,29621,29622,29624,29626,29628,29631,29633,29635,29637,29639,29641,29643,29645,29647,29649],{"class":1088,"line":1288},[1086,29623,10725],{"class":1105},[1086,29625,1398],{"class":1146},[1086,29627,5962],{"class":1155},[1086,29629,29630],{"class":1096},"\"Detected language: ",[1086,29632,4409],{"class":1187},[1086,29634,1473],{"class":1105},[1086,29636,4340],{"class":1146},[1086,29638,10742],{"class":1146},[1086,29640,28966],{"class":1096},[1086,29642,10742],{"class":1146},[1086,29644,4420],{"class":1146},[1086,29646,4423],{"class":1187},[1086,29648,1159],{"class":1096},[1086,29650,1455],{"class":1146},[1086,29652,29653,29655,29657,29659,29662,29664,29666,29668,29670,29672,29674,29676,29678,29680],{"class":1088,"line":2685},[1086,29654,10725],{"class":1105},[1086,29656,1398],{"class":1146},[1086,29658,5962],{"class":1155},[1086,29660,29661],{"class":1096},"\"Text: ",[1086,29663,4409],{"class":1187},[1086,29665,1473],{"class":1105},[1086,29667,4340],{"class":1146},[1086,29669,10742],{"class":1146},[1086,29671,1018],{"class":1096},[1086,29673,10742],{"class":1146},[1086,29675,4420],{"class":1146},[1086,29677,4423],{"class":1187},[1086,29679,1159],{"class":1096},[1086,29681,1455],{"class":1146},[4937,29683],{},[863,29685,29687],{"id":29686},"tips-and-optimizations","Tips and Optimizations",[1074,29689,29691],{"id":29690},"_1-faster-transcription-on-apple-silicon","1. Faster Transcription on Apple Silicon",[1013,29693,29695],{"className":1080,"code":29694,"language":1082,"meta":728,"style":728},"pip install mlx-whisper\n",[895,29696,29697],{"__ignoreMap":728},[1086,29698,29699,29701,29703],{"class":1088,"line":1089},[1086,29700,28074],{"class":1092},[1086,29702,27993],{"class":1096},[1086,29704,29705],{"class":1096}," mlx-whisper\n",[1013,29707,29709],{"className":1368,"code":29708,"language":1250,"meta":728,"style":728},"import mlx_whisper\n\nresult = mlx_whisper.transcribe(\n    \"audio.mp3\",\n    path_or_hf_repo=\"mlx-community/whisper-medium-mlx\"\n)\n",[895,29710,29711,29718,29722,29737,29748,29762],{"__ignoreMap":728},[1086,29712,29713,29715],{"class":1088,"line":1089},[1086,29714,6503],{"class":1423},[1086,29716,29717],{"class":1436}," mlx_whisper\n",[1086,29719,29720],{"class":1088,"line":729},[1086,29721,3390],{"emptyLinePlaceholder":738},[1086,29723,29724,29726,29728,29731,29733,29735],{"class":1088,"line":1112},[1086,29725,29358],{"class":1436},[1086,29727,1440],{"class":1146},[1086,29729,29730],{"class":1436}," mlx_whisper",[1086,29732,861],{"class":1146},[1086,29734,28952],{"class":1105},[1086,29736,4094],{"class":1146},[1086,29738,29739,29741,29744,29746],{"class":1088,"line":1181},[1086,29740,1169],{"class":1146},[1086,29742,29743],{"class":1096},"audio.mp3",[1086,29745,1159],{"class":1146},[1086,29747,1202],{"class":1146},[1086,29749,29750,29753,29755,29757,29760],{"class":1088,"line":1205},[1086,29751,29752],{"class":1401},"    path_or_hf_repo",[1086,29754,1440],{"class":1146},[1086,29756,1159],{"class":1146},[1086,29758,29759],{"class":1096},"mlx-community/whisper-medium-mlx",[1086,29761,4441],{"class":1146},[1086,29763,29764],{"class":1088,"line":1276},[1086,29765,1455],{"class":1146},[1074,29767,29769],{"id":29768},"_2-handling-long-recordings","2. Handling Long Recordings",[842,29771,29772],{},"For recordings over 1 hour, consider splitting:",[1013,29774,29776],{"className":1080,"code":29775,"language":1082,"meta":728,"style":728},"ffmpeg -i long_recording.mp3 -f segment -segment_time 1800 -c copy chunk_%03d.mp3\n",[895,29777,29778],{"__ignoreMap":728},[1086,29779,29780,29782,29784,29787,29790,29792,29795,29798,29800,29803],{"class":1088,"line":1089},[1086,29781,28052],{"class":1092},[1086,29783,28132],{"class":1096},[1086,29785,29786],{"class":1096}," long_recording.mp3",[1086,29788,29789],{"class":1096}," -f",[1086,29791,29430],{"class":1096},[1086,29793,29794],{"class":1096}," -segment_time",[1086,29796,29797],{"class":1187}," 1800",[1086,29799,8574],{"class":1096},[1086,29801,29802],{"class":1096}," copy",[1086,29804,29805],{"class":1096}," chunk_%03d.mp3\n",[1074,29807,29809],{"id":29808},"_3-improving-quality-for-difficult-audio","3. Improving Quality for Difficult Audio",[958,29811,29812,29819],{},[961,29813,29814,29815,20248,29817,1410],{},"Use a larger model (",[895,29816,28348],{},[895,29818,28331],{},[961,29820,29821,29822,1133],{},"Add context with ",[895,29823,29824],{},"initial_prompt",[1013,29826,29828],{"className":1368,"code":29827,"language":1250,"meta":728,"style":728},"result = model.transcribe(\n    \"audio.mp3\",\n    language=\"en\",\n    initial_prompt=\"This is a conversation about swimming training and interval timers.\"\n)\n",[895,29829,29830,29844,29854,29868,29882],{"__ignoreMap":728},[1086,29831,29832,29834,29836,29838,29840,29842],{"class":1088,"line":1089},[1086,29833,29358],{"class":1436},[1086,29835,1440],{"class":1146},[1086,29837,28947],{"class":1436},[1086,29839,861],{"class":1146},[1086,29841,28952],{"class":1105},[1086,29843,4094],{"class":1146},[1086,29845,29846,29848,29850,29852],{"class":1088,"line":729},[1086,29847,1169],{"class":1146},[1086,29849,29743],{"class":1096},[1086,29851,1159],{"class":1146},[1086,29853,1202],{"class":1146},[1086,29855,29856,29858,29860,29862,29864,29866],{"class":1088,"line":1112},[1086,29857,28843],{"class":1401},[1086,29859,1440],{"class":1146},[1086,29861,1159],{"class":1146},[1086,29863,28854],{"class":1096},[1086,29865,1159],{"class":1146},[1086,29867,1202],{"class":1146},[1086,29869,29870,29873,29875,29877,29880],{"class":1088,"line":1181},[1086,29871,29872],{"class":1401},"    initial_prompt",[1086,29874,1440],{"class":1146},[1086,29876,1159],{"class":1146},[1086,29878,29879],{"class":1096},"This is a conversation about swimming training and interval timers.",[1086,29881,4441],{"class":1146},[1086,29883,29884],{"class":1088,"line":1205},[1086,29885,1455],{"class":1146},[4937,29887],{},[863,29889,29891],{"id":29890},"comparison-with-alternatives","Comparison with Alternatives",[871,29893,29894,29908],{},[874,29895,29896],{},[877,29897,29898,29900,29903,29906],{},[880,29899,27663],{},[880,29901,29902],{},"Cost",[880,29904,29905],{},"Privacy",[880,29907,28274],{},[887,29909,29910,29923,29937,29949],{},[877,29911,29912,29915,29918,29921],{},[892,29913,29914],{},"Whisper (local)",[892,29916,29917],{},"Free",[892,29919,29920],{},"Full privacy",[892,29922,28340],{},[877,29924,29925,29928,29931,29934],{},[892,29926,29927],{},"OpenAI API",[892,29929,29930],{},"$0.006/min",[892,29932,29933],{},"Cloud-based",[892,29935,29936],{},"Excellent",[877,29938,29939,29942,29945,29947],{},[892,29940,29941],{},"Google Speech-to-Text",[892,29943,29944],{},"$0.016/min",[892,29946,29933],{},[892,29948,28340],{},[877,29950,29951,29954,29957,29959],{},[892,29952,29953],{},"AssemblyAI",[892,29955,29956],{},"$0.015/min",[892,29958,29933],{},[892,29960,28340],{},[842,29962,29963],{},"Choose local Whisper when:",[958,29965,29966,29969,29972],{},[961,29967,29968],{},"Data privacy matters",[961,29970,29971],{},"You have lots of content to transcribe",[961,29973,29974],{},"You want to avoid recurring costs",[4937,29976],{},[863,29978,25699],{"id":8196},[842,29980,29981],{},"The video → mp3 → text pipeline with Whisper is straightforward:",[991,29983,29984,29992],{},[961,29985,29986,27851,29989],{},[996,29987,29988],{},"Extract audio",[895,29990,29991],{},"ffmpeg -i video.mp4 -vn audio.mp3",[961,29993,29994,27851,29997],{},[996,29995,29996],{},"Transcribe",[895,29998,29999],{},"whisper audio.mp3 --language English --model medium",[842,30001,30002],{},"Everything runs locally, it's free, and delivers production-quality results.",[4937,30004],{},[863,30006,18693],{"id":18692},[958,30008,30009,30016,30023],{},[961,30010,30011],{},[846,30012,30015],{"href":30013,"rel":30014},"https://github.com/openai/whisper",[850],"OpenAI Whisper GitHub",[961,30017,30018],{},[846,30019,30022],{"href":30020,"rel":30021},"https://ffmpeg.org/documentation.html",[850],"FFmpeg Documentation",[961,30024,30025],{},[846,30026,30029],{"href":30027,"rel":30028},"https://github.com/openai/whisper/blob/main/model-card.md",[850],"Whisper Model Card",[1680,30031,30032],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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}",{"title":728,"searchDepth":729,"depth":729,"links":30034},[30035,30036,30037,30042,30043,30044,30049,30054,30055,30056,30057,30062,30063,30064],{"id":27939,"depth":729,"text":27940},{"id":27955,"depth":729,"text":27956},{"id":27972,"depth":729,"text":27973,"children":30038},[30039,30040,30041],{"id":27979,"depth":1112,"text":27980},{"id":27999,"depth":1112,"text":28000},{"id":28029,"depth":1112,"text":28030},{"id":28060,"depth":729,"text":28061},{"id":28116,"depth":729,"text":28117},{"id":28223,"depth":729,"text":28224,"children":30045},[30046,30047,30048],{"id":28227,"depth":1112,"text":28228},{"id":28256,"depth":1112,"text":28257},{"id":28372,"depth":1112,"text":28373},{"id":28421,"depth":729,"text":28422,"children":30050},[30051,30052,30053],{"id":28428,"depth":1112,"text":28429},{"id":28459,"depth":1112,"text":28460},{"id":28487,"depth":1112,"text":28488},{"id":28506,"depth":729,"text":28507},{"id":29311,"depth":729,"text":29312},{"id":29542,"depth":729,"text":29543},{"id":29686,"depth":729,"text":29687,"children":30058},[30059,30060,30061],{"id":29690,"depth":1112,"text":29691},{"id":29768,"depth":1112,"text":29769},{"id":29808,"depth":1112,"text":29809},{"id":29890,"depth":729,"text":29891},{"id":8196,"depth":729,"text":25699},{"id":18692,"depth":729,"text":18693},"2026-02-03T00:00:00.000Z","A practical guide to the video → mp3 → text pipeline using OpenAI Whisper. Free, local transcription for interviews, podcasts, and meetings.",{"src":30068},"/images/blog/musictechlab_blog_how-to-transcribe-video-to-text-using-whisper.webp",{"enabled":738,"items":30070},[30071,30073,30075,30077],{"text":30072,"icon":7495},"Whisper runs locally and for free, with no data sent to the cloud.",{"text":30074,"icon":2939},"The medium model offers the best quality-to-speed ratio for most transcription tasks.",{"text":30076,"icon":1723},"FFmpeg extracts audio from video in one command before Whisper processes it.",{"text":30078,"icon":1067},"Supports 90+ languages with automatic language detection when the source is unknown.",{},{"title":144,"description":30066},[14100,18784],"BuviUKMZPE9TPst9p1kaETZ_AO8QysHB5AZaUwEOroo",{"id":30084,"title":136,"authors":30085,"badge":30088,"body":30089,"category":731,"client":723,"date":31795,"description":31796,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":31797,"keyTakeaways":31799,"meta":31811,"navigation":738,"path":137,"seo":31812,"status":723,"stem":138,"tags":31813,"teaser":723,"__hash__":31814},"posts/blog/music-data/extracting-data-from-ableton-als-asd-files.md",[30086],{"name":834,"to":720,"avatar":30087},{"src":722},{"label":837,"color":838},{"type":725,"value":30090,"toc":31769},[30091,30106,30109,30113,30116,30163,30180,30184,30190,30194,30274,30278,30281,30295,30298,30302,30309,30509,30512,30521,30525,30530,30534,30599,30603,30609,30847,30868,30877,30881,30885,30888,30897,30900,30926,30928,30931,31336,31339,31350,31354,31363,31366,31386,31390,31393,31420,31424,31461,31483,31487,31519,31523,31551,31555,31656,31660,31663,31691,31694,31697,31723,31725,31727,31751,31753,31766],[842,30092,30093,30094,30097,30098,30101,30102,30105],{},"If you've ever wondered what data Ableton Live stores in its project files, you're not alone. As part of our work at MusicTech Lab, we needed to extract metadata from Ableton projects – tempo, time signature, track names, and song sections – to power our music analysis workflows. The result is ",[996,30095,30096],{},"MTL Ableton Analyser",", an open-source Python tool that parses both ",[895,30099,30100],{},".als"," (Ableton Live Set) and ",[895,30103,30104],{},".asd"," (Ableton Sample Data) files.",[842,30107,30108],{},"In this article, we'll explore what's inside these files and how to extract useful data from them programmatically.",[863,30110,30112],{"id":30111},"understanding-abletons-file-formats","Understanding Ableton's File Formats",[842,30114,30115],{},"Ableton Live uses two primary file formats for storing project and analysis data:",[871,30117,30118,30131],{},[874,30119,30120],{},[877,30121,30122,30124,30127,30129],{},[880,30123,14141],{},[880,30125,30126],{},"Full Name",[880,30128,8043],{},[880,30130,3021],{},[887,30132,30133,30148],{},[877,30134,30135,30139,30142,30145],{},[892,30136,30137],{},[895,30138,30100],{},[892,30140,30141],{},"Ableton Live Set",[892,30143,30144],{},"Gzip-compressed XML",[892,30146,30147],{},"Complete project data – tracks, clips, tempo, arrangement",[877,30149,30150,30154,30157,30160],{},[892,30151,30152],{},[895,30153,30104],{},[892,30155,30156],{},"Ableton Sample Data",[892,30158,30159],{},"Binary",[892,30161,30162],{},"Analysis cache – detected BPM, warp markers, waveform peaks",[842,30164,30165,30166,30168,30169,30172,30173,30175,30176,30179],{},"The key insight is that ",[895,30167,30100],{}," files are actually ",[996,30170,30171],{},"compressed XML",", making them relatively straightforward to parse. The ",[895,30174,30104],{}," files, however, are ",[996,30177,30178],{},"proprietary binary"," and require reverse-engineering to extract data.",[863,30181,30183],{"id":30182},"data-extractable-from-als-files","Data Extractable from .als Files",[842,30185,30186,30187,30189],{},"When you decompress an ",[895,30188,30100],{}," file, you get a well-structured XML document containing the entire project state. Here's what we can reliably extract:",[1074,30191,30193],{"id":30192},"project-metadata","Project Metadata",[871,30195,30196,30208],{},[874,30197,30198],{},[877,30199,30200,30203,30205],{},[880,30201,30202],{},"Data Point",[880,30204,7624],{},[880,30206,30207],{},"XML Path",[887,30209,30210,30225,30240,30255],{},[877,30211,30212,30217,30220],{},[892,30213,30214],{},[996,30215,30216],{},"Tempo (BPM)",[892,30218,30219],{},"Project tempo",[892,30221,30222],{},[895,30223,30224],{},".//Tempo/Manual",[877,30226,30227,30232,30235],{},[892,30228,30229],{},[996,30230,30231],{},"Time Signature",[892,30233,30234],{},"Numerator and denominator",[892,30236,30237],{},[895,30238,30239],{},".//TimeSignature/*",[877,30241,30242,30247,30250],{},[892,30243,30244],{},[996,30245,30246],{},"Track Count",[892,30248,30249],{},"Number of tracks in project",[892,30251,30252],{},[895,30253,30254],{},".//Tracks",[877,30256,30257,30262,30265],{},[892,30258,30259],{},[996,30260,30261],{},"Track Names",[892,30263,30264],{},"User-defined track names",[892,30266,30267,5660,30270,30273],{},[895,30268,30269],{},"EffectiveName",[895,30271,30272],{},"UserName"," attributes",[1074,30275,30277],{"id":30276},"warp-markers","Warp Markers",[842,30279,30280],{},"Warp markers define the relationship between audio time (seconds) and musical time (beats). Each marker contains:",[958,30282,30283,30289],{},[961,30284,30285,30288],{},[996,30286,30287],{},"SecTime"," – Position in the audio file (seconds)",[961,30290,30291,30294],{},[996,30292,30293],{},"BeatTime"," – Corresponding position in beats",[842,30296,30297],{},"This data is essential for beat-synced playback and tempo manipulation.",[1074,30299,30301],{"id":30300},"locators-section-markers","Locators (Section Markers)",[842,30303,30304,30305,30308],{},"Perhaps the most valuable data for music analysis – ",[996,30306,30307],{},"locators"," represent named positions in your arrangement:",[1013,30310,30312],{"className":1136,"code":30311,"language":1139,"meta":728,"style":728},"{\n  \"sections\": [\n    { \"name\": \"INT1\", \"beat\": 0 },\n    { \"name\": \"VER1\", \"beat\": 32 },\n    { \"name\": \"CHO1\", \"beat\": 64 },\n    { \"name\": \"BRD1\", \"beat\": 96 },\n    { \"name\": \"OUT1\", \"beat\": 128 }\n  ]\n}\n",[895,30313,30314,30318,30331,30366,30400,30434,30468,30501,30505],{"__ignoreMap":728},[1086,30315,30316],{"class":1088,"line":1089},[1086,30317,1147],{"class":1146},[1086,30319,30320,30322,30325,30327,30329],{"class":1088,"line":729},[1086,30321,1152],{"class":1146},[1086,30323,30324],{"class":1155},"sections",[1086,30326,1159],{"class":1146},[1086,30328,1133],{"class":1146},[1086,30330,6580],{"class":1146},[1086,30332,30333,30336,30338,30340,30342,30344,30346,30349,30351,30353,30355,30358,30360,30362,30364],{"class":1088,"line":1112},[1086,30334,30335],{"class":1146},"    {",[1086,30337,1195],{"class":1146},[1086,30339,4184],{"class":1092},[1086,30341,1159],{"class":1146},[1086,30343,1133],{"class":1146},[1086,30345,1195],{"class":1146},[1086,30347,30348],{"class":1096},"INT1",[1086,30350,1159],{"class":1146},[1086,30352,1227],{"class":1146},[1086,30354,1195],{"class":1146},[1086,30356,30357],{"class":1092},"beat",[1086,30359,1159],{"class":1146},[1086,30361,1133],{"class":1146},[1086,30363,22718],{"class":1187},[1086,30365,4792],{"class":1146},[1086,30367,30368,30370,30372,30374,30376,30378,30380,30383,30385,30387,30389,30391,30393,30395,30398],{"class":1088,"line":1181},[1086,30369,30335],{"class":1146},[1086,30371,1195],{"class":1146},[1086,30373,4184],{"class":1092},[1086,30375,1159],{"class":1146},[1086,30377,1133],{"class":1146},[1086,30379,1195],{"class":1146},[1086,30381,30382],{"class":1096},"VER1",[1086,30384,1159],{"class":1146},[1086,30386,1227],{"class":1146},[1086,30388,1195],{"class":1146},[1086,30390,30357],{"class":1092},[1086,30392,1159],{"class":1146},[1086,30394,1133],{"class":1146},[1086,30396,30397],{"class":1187}," 32",[1086,30399,4792],{"class":1146},[1086,30401,30402,30404,30406,30408,30410,30412,30414,30417,30419,30421,30423,30425,30427,30429,30432],{"class":1088,"line":1205},[1086,30403,30335],{"class":1146},[1086,30405,1195],{"class":1146},[1086,30407,4184],{"class":1092},[1086,30409,1159],{"class":1146},[1086,30411,1133],{"class":1146},[1086,30413,1195],{"class":1146},[1086,30415,30416],{"class":1096},"CHO1",[1086,30418,1159],{"class":1146},[1086,30420,1227],{"class":1146},[1086,30422,1195],{"class":1146},[1086,30424,30357],{"class":1092},[1086,30426,1159],{"class":1146},[1086,30428,1133],{"class":1146},[1086,30430,30431],{"class":1187}," 64",[1086,30433,4792],{"class":1146},[1086,30435,30436,30438,30440,30442,30444,30446,30448,30451,30453,30455,30457,30459,30461,30463,30466],{"class":1088,"line":1276},[1086,30437,30335],{"class":1146},[1086,30439,1195],{"class":1146},[1086,30441,4184],{"class":1092},[1086,30443,1159],{"class":1146},[1086,30445,1133],{"class":1146},[1086,30447,1195],{"class":1146},[1086,30449,30450],{"class":1096},"BRD1",[1086,30452,1159],{"class":1146},[1086,30454,1227],{"class":1146},[1086,30456,1195],{"class":1146},[1086,30458,30357],{"class":1092},[1086,30460,1159],{"class":1146},[1086,30462,1133],{"class":1146},[1086,30464,30465],{"class":1187}," 96",[1086,30467,4792],{"class":1146},[1086,30469,30470,30472,30474,30476,30478,30480,30482,30485,30487,30489,30491,30493,30495,30497,30499],{"class":1088,"line":1282},[1086,30471,30335],{"class":1146},[1086,30473,1195],{"class":1146},[1086,30475,4184],{"class":1092},[1086,30477,1159],{"class":1146},[1086,30479,1133],{"class":1146},[1086,30481,1195],{"class":1146},[1086,30483,30484],{"class":1096},"OUT1",[1086,30486,1159],{"class":1146},[1086,30488,1227],{"class":1146},[1086,30490,1195],{"class":1146},[1086,30492,30357],{"class":1092},[1086,30494,1159],{"class":1146},[1086,30496,1133],{"class":1146},[1086,30498,9016],{"class":1187},[1086,30500,27132],{"class":1146},[1086,30502,30503],{"class":1088,"line":1288},[1086,30504,9465],{"class":1146},[1086,30506,30507],{"class":1088,"line":2685},[1086,30508,1291],{"class":1146},[842,30510,30511],{},"These locators, when combined with tempo data, give you a complete song structure map with precise timestamps.",[1045,30513,30515],{"className":30514},[13033,13034,1052],[842,30516,30517],{},[1027,30518],{"alt":30519,"src":30520,"width":1322},"ALS Analysis Output","/images/blog/musictechlab_blog_ableton_als_analysis.webp",[863,30522,30524],{"id":30523},"data-extractable-from-asd-files","Data Extractable from .asd Files",[842,30526,5119,30527,30529],{},[895,30528,30104],{}," format is more challenging. Ableton creates these files automatically when you analyze audio, storing:",[1074,30531,30533],{"id":30532},"detected-metadata","Detected Metadata",[871,30535,30536,30547],{},[874,30537,30538],{},[877,30539,30540,30542,30545],{},[880,30541,30202],{},[880,30543,30544],{},"Detection Method",[880,30546,2719],{},[887,30548,30549,30562,30574,30586],{},[877,30550,30551,30556,30559],{},[892,30552,30553],{},[996,30554,30555],{},"Format Version",[892,30557,30558],{},"First byte",[892,30560,30561],{},"Typically version 6",[877,30563,30564,30568,30571],{},[892,30565,30566],{},[996,30567,30216],{},[892,30569,30570],{},"Binary search at known offsets",[892,30572,30573],{},"Ableton's auto-detected BPM",[877,30575,30576,30580,30583],{},[892,30577,30578],{},[996,30579,30231],{},[892,30581,30582],{},"Marker string search",[892,30584,30585],{},"Numerator/Denominator extraction",[877,30587,30588,30593,30596],{},[892,30589,30590],{},[996,30591,30592],{},"Waveform Data",[892,30594,30595],{},"Presence check",[892,30597,30598],{},"Boolean – are peaks pre-computed?",[1074,30600,30602],{"id":30601},"bpm-detection-strategy","BPM Detection Strategy",[842,30604,30605,30606,30608],{},"The BPM value in ",[895,30607,30104],{}," files isn't at a fixed offset. Our parser searches multiple known locations:",[1013,30610,30612],{"className":1368,"code":30611,"language":1250,"meta":728,"style":728},"KNOWN_BPM_OFFSETS = [7736, 8448, 8632, 7685, 7740, 8440]\n\ndef find_bpm(data: bytes) -> Optional[float]:\n    for offset in KNOWN_BPM_OFFSETS:\n        if offset + 4 \u003C= len(data):\n            value = struct.unpack('\u003Cf', data[offset:offset+4])[0]\n            if 60.0 \u003C= value \u003C= 200.0:\n                return round(value, 2)\n    # Fallback: scan byte range for valid BPM\n    return scan_for_bpm(data, range_start=6000, range_end=9500)\n",[895,30613,30614,30653,30657,30686,30700,30724,30772,30792,30809,30814],{"__ignoreMap":728},[1086,30615,30616,30619,30621,30623,30626,30628,30631,30633,30636,30638,30641,30643,30646,30648,30651],{"class":1088,"line":1089},[1086,30617,30618],{"class":1436},"KNOWN_BPM_OFFSETS ",[1086,30620,1440],{"class":1146},[1086,30622,1217],{"class":1146},[1086,30624,30625],{"class":1187},"7736",[1086,30627,1227],{"class":1146},[1086,30629,30630],{"class":1187}," 8448",[1086,30632,1227],{"class":1146},[1086,30634,30635],{"class":1187}," 8632",[1086,30637,1227],{"class":1146},[1086,30639,30640],{"class":1187}," 7685",[1086,30642,1227],{"class":1146},[1086,30644,30645],{"class":1187}," 7740",[1086,30647,1227],{"class":1146},[1086,30649,30650],{"class":1187}," 8440",[1086,30652,1273],{"class":1146},[1086,30654,30655],{"class":1088,"line":729},[1086,30656,3390],{"emptyLinePlaceholder":738},[1086,30658,30659,30661,30664,30666,30668,30670,30672,30674,30676,30679,30681,30684],{"class":1088,"line":1112},[1086,30660,1392],{"class":1155},[1086,30662,30663],{"class":1105}," find_bpm",[1086,30665,1398],{"class":1146},[1086,30667,4337],{"class":1401},[1086,30669,1133],{"class":1146},[1086,30671,20298],{"class":1092},[1086,30673,1410],{"class":1146},[1086,30675,1413],{"class":1146},[1086,30677,30678],{"class":1436}," Optional",[1086,30680,4340],{"class":1146},[1086,30682,30683],{"class":1092},"float",[1086,30685,10888],{"class":1146},[1086,30687,30688,30690,30693,30695,30698],{"class":1088,"line":1181},[1086,30689,5925],{"class":1423},[1086,30691,30692],{"class":1436}," offset ",[1086,30694,5931],{"class":1423},[1086,30696,30697],{"class":1436}," KNOWN_BPM_OFFSETS",[1086,30699,1418],{"class":1146},[1086,30701,30702,30704,30706,30709,30712,30715,30718,30720,30722],{"class":1088,"line":1205},[1086,30703,6800],{"class":1423},[1086,30705,30692],{"class":1436},[1086,30707,30708],{"class":1146},"+",[1086,30710,30711],{"class":1187}," 4",[1086,30713,30714],{"class":1146}," \u003C=",[1086,30716,30717],{"class":1105}," len",[1086,30719,1398],{"class":1146},[1086,30721,4337],{"class":1105},[1086,30723,4047],{"class":1146},[1086,30725,30726,30729,30731,30734,30736,30739,30741,30743,30746,30748,30750,30752,30754,30757,30759,30761,30763,30765,30768,30770],{"class":1088,"line":1276},[1086,30727,30728],{"class":1436},"            value ",[1086,30730,1440],{"class":1146},[1086,30732,30733],{"class":1436}," struct",[1086,30735,861],{"class":1146},[1086,30737,30738],{"class":1105},"unpack",[1086,30740,1398],{"class":1146},[1086,30742,10742],{"class":1146},[1086,30744,30745],{"class":1096},"\u003Cf",[1086,30747,10742],{"class":1146},[1086,30749,1227],{"class":1146},[1086,30751,20125],{"class":1105},[1086,30753,4340],{"class":1146},[1086,30755,30756],{"class":1105},"offset",[1086,30758,1133],{"class":1146},[1086,30760,30756],{"class":1105},[1086,30762,30708],{"class":1146},[1086,30764,7989],{"class":1187},[1086,30766,30767],{"class":1146},"])[",[1086,30769,4417],{"class":1187},[1086,30771,1273],{"class":1146},[1086,30773,30774,30776,30779,30781,30784,30787,30790],{"class":1088,"line":1282},[1086,30775,6918],{"class":1423},[1086,30777,30778],{"class":1187}," 60.0",[1086,30780,30714],{"class":1146},[1086,30782,30783],{"class":1436}," value ",[1086,30785,30786],{"class":1146},"\u003C=",[1086,30788,30789],{"class":1187}," 200.0",[1086,30791,1418],{"class":1146},[1086,30793,30794,30796,30799,30801,30803,30805,30807],{"class":1088,"line":1288},[1086,30795,24243],{"class":1423},[1086,30797,30798],{"class":1105}," round",[1086,30800,1398],{"class":1146},[1086,30802,26662],{"class":1105},[1086,30804,1227],{"class":1146},[1086,30806,9048],{"class":1187},[1086,30808,1455],{"class":1146},[1086,30810,30811],{"class":1088,"line":2685},[1086,30812,30813],{"class":1427},"    # Fallback: scan byte range for valid BPM\n",[1086,30815,30816,30818,30821,30823,30825,30827,30830,30832,30835,30837,30840,30842,30845],{"class":1088,"line":2700},[1086,30817,1460],{"class":1423},[1086,30819,30820],{"class":1105}," scan_for_bpm",[1086,30822,1398],{"class":1146},[1086,30824,4337],{"class":1105},[1086,30826,1227],{"class":1146},[1086,30828,30829],{"class":1401}," range_start",[1086,30831,1440],{"class":1146},[1086,30833,30834],{"class":1187},"6000",[1086,30836,1227],{"class":1146},[1086,30838,30839],{"class":1401}," range_end",[1086,30841,1440],{"class":1146},[1086,30843,30844],{"class":1187},"9500",[1086,30846,1455],{"class":1146},[1572,30848,30849],{},[842,30850,30851,30854,30855,30857,30858,30861,30862,30864,30865,30867],{},[996,30852,30853],{},"Important:"," The BPM in ",[895,30856,30104],{}," files represents Ableton's ",[996,30859,30860],{},"auto-detection",", which may differ from the project tempo in the ",[895,30863,30100],{}," file. The ",[895,30866,30100],{}," tempo is always the authoritative value.",[1045,30869,30871],{"className":30870},[13033,13034,1052],[842,30872,30873],{},[1027,30874],{"alt":30875,"src":30876,"width":1322},"ASD Analysis Output","/images/blog/musictechlab_blog_ableton_asd_analysis.webp",[863,30878,30880],{"id":30879},"practical-applications","Practical Applications",[1074,30882,30884],{"id":30883},"waveform-visualization-with-sections","Waveform Visualization with Sections",[842,30886,30887],{},"By combining data from both file types, we can generate rich visualizations:",[1045,30889,30891],{"className":30890},[13033,13034,1052],[842,30892,30893],{},[1027,30894],{"alt":30884,"src":30895,"width":30896},"/images/blog/musictechlab_blog_ableton_asd_waveform.webp",800,[842,30898,30899],{},"The visualization includes:",[958,30901,30902,30908,30914,30920],{},[961,30903,30904,30907],{},[996,30905,30906],{},"Waveform amplitude"," – extracted from the audio file",[961,30909,30910,30913],{},[996,30911,30912],{},"Beat grid"," – calculated from BPM and time signature",[961,30915,30916,30919],{},[996,30917,30918],{},"Section overlays"," – color-coded backgrounds from locator data",[961,30921,30922,30925],{},[996,30923,30924],{},"Downbeat markers"," – emphasized beats at bar boundaries",[1074,30927,27388],{"id":27387},[842,30929,30930],{},"For web applications, we export a JSON format compatible with WaveSurfer.js:",[1013,30932,30934],{"className":1136,"code":30933,"language":1139,"meta":728,"style":728},"{\n  \"version\": \"1.0\",\n  \"filename\": \"track.mp3\",\n  \"duration\": 180.5,\n  \"sampleRate\": 44100,\n  \"bpm\": 128,\n  \"beatsPerBar\": 4,\n  \"totalBeats\": 385,\n  \"peaks\": [0.12, 0.45, 0.89, ...],\n  \"beats\": [\n    { \"time\": 0.0, \"beat\": 1, \"isDownbeat\": true },\n    { \"time\": 0.469, \"beat\": 2, \"isDownbeat\": false }\n  ],\n  \"sections\": [\n    { \"name\": \"INT1\", \"start\": 0.0, \"end\": 15.0, \"color\": \"#1E3A5F\" },\n    { \"name\": \"VER1\", \"start\": 15.0, \"end\": 30.0, \"color\": \"#3D1A5F\" }\n  ]\n}\n",[895,30935,30936,30940,30959,30978,30994,31009,31024,31039,31055,31088,31101,31144,31186,31191,31203,31266,31328,31332],{"__ignoreMap":728},[1086,30937,30938],{"class":1088,"line":1089},[1086,30939,1147],{"class":1146},[1086,30941,30942,30944,30946,30948,30950,30952,30955,30957],{"class":1088,"line":729},[1086,30943,1152],{"class":1146},[1086,30945,15658],{"class":1155},[1086,30947,1159],{"class":1146},[1086,30949,1133],{"class":1146},[1086,30951,1195],{"class":1146},[1086,30953,30954],{"class":1096},"1.0",[1086,30956,1159],{"class":1146},[1086,30958,1202],{"class":1146},[1086,30960,30961,30963,30965,30967,30969,30971,30974,30976],{"class":1088,"line":1112},[1086,30962,1152],{"class":1146},[1086,30964,20469],{"class":1155},[1086,30966,1159],{"class":1146},[1086,30968,1133],{"class":1146},[1086,30970,1195],{"class":1146},[1086,30972,30973],{"class":1096},"track.mp3",[1086,30975,1159],{"class":1146},[1086,30977,1202],{"class":1146},[1086,30979,30980,30982,30985,30987,30989,30992],{"class":1088,"line":1181},[1086,30981,1152],{"class":1146},[1086,30983,30984],{"class":1155},"duration",[1086,30986,1159],{"class":1146},[1086,30988,1133],{"class":1146},[1086,30990,30991],{"class":1187}," 180.5",[1086,30993,1202],{"class":1146},[1086,30995,30996,30998,31001,31003,31005,31007],{"class":1088,"line":1205},[1086,30997,1152],{"class":1146},[1086,30999,31000],{"class":1155},"sampleRate",[1086,31002,1159],{"class":1146},[1086,31004,1133],{"class":1146},[1086,31006,9032],{"class":1187},[1086,31008,1202],{"class":1146},[1086,31010,31011,31013,31016,31018,31020,31022],{"class":1088,"line":1276},[1086,31012,1152],{"class":1146},[1086,31014,31015],{"class":1155},"bpm",[1086,31017,1159],{"class":1146},[1086,31019,1133],{"class":1146},[1086,31021,9016],{"class":1187},[1086,31023,1202],{"class":1146},[1086,31025,31026,31028,31031,31033,31035,31037],{"class":1088,"line":1282},[1086,31027,1152],{"class":1146},[1086,31029,31030],{"class":1155},"beatsPerBar",[1086,31032,1159],{"class":1146},[1086,31034,1133],{"class":1146},[1086,31036,30711],{"class":1187},[1086,31038,1202],{"class":1146},[1086,31040,31041,31043,31046,31048,31050,31053],{"class":1088,"line":1288},[1086,31042,1152],{"class":1146},[1086,31044,31045],{"class":1155},"totalBeats",[1086,31047,1159],{"class":1146},[1086,31049,1133],{"class":1146},[1086,31051,31052],{"class":1187}," 385",[1086,31054,1202],{"class":1146},[1086,31056,31057,31059,31062,31064,31066,31068,31071,31073,31076,31078,31081,31083,31086],{"class":1088,"line":2685},[1086,31058,1152],{"class":1146},[1086,31060,31061],{"class":1155},"peaks",[1086,31063,1159],{"class":1146},[1086,31065,1133],{"class":1146},[1086,31067,1217],{"class":1146},[1086,31069,31070],{"class":1187},"0.12",[1086,31072,1227],{"class":1146},[1086,31074,31075],{"class":1187}," 0.45",[1086,31077,1227],{"class":1146},[1086,31079,31080],{"class":1187}," 0.89",[1086,31082,1227],{"class":1146},[1086,31084,31085],{"class":1436}," ...",[1086,31087,9297],{"class":1146},[1086,31089,31090,31092,31095,31097,31099],{"class":1088,"line":2700},[1086,31091,1152],{"class":1146},[1086,31093,31094],{"class":1155},"beats",[1086,31096,1159],{"class":1146},[1086,31098,1133],{"class":1146},[1086,31100,6580],{"class":1146},[1086,31102,31103,31105,31107,31110,31112,31114,31117,31119,31121,31123,31125,31127,31129,31131,31133,31136,31138,31140,31142],{"class":1088,"line":3398},[1086,31104,30335],{"class":1146},[1086,31106,1195],{"class":1146},[1086,31108,31109],{"class":1092},"time",[1086,31111,1159],{"class":1146},[1086,31113,1133],{"class":1146},[1086,31115,31116],{"class":1187}," 0.0",[1086,31118,1227],{"class":1146},[1086,31120,1195],{"class":1146},[1086,31122,30357],{"class":1092},[1086,31124,1159],{"class":1146},[1086,31126,1133],{"class":1146},[1086,31128,23488],{"class":1187},[1086,31130,1227],{"class":1146},[1086,31132,1195],{"class":1146},[1086,31134,31135],{"class":1092},"isDownbeat",[1086,31137,1159],{"class":1146},[1086,31139,1133],{"class":1146},[1086,31141,27022],{"class":1146},[1086,31143,4792],{"class":1146},[1086,31145,31146,31148,31150,31152,31154,31156,31159,31161,31163,31165,31167,31169,31171,31173,31175,31177,31179,31181,31184],{"class":1088,"line":1715},[1086,31147,30335],{"class":1146},[1086,31149,1195],{"class":1146},[1086,31151,31109],{"class":1092},[1086,31153,1159],{"class":1146},[1086,31155,1133],{"class":1146},[1086,31157,31158],{"class":1187}," 0.469",[1086,31160,1227],{"class":1146},[1086,31162,1195],{"class":1146},[1086,31164,30357],{"class":1092},[1086,31166,1159],{"class":1146},[1086,31168,1133],{"class":1146},[1086,31170,9048],{"class":1187},[1086,31172,1227],{"class":1146},[1086,31174,1195],{"class":1146},[1086,31176,31135],{"class":1092},[1086,31178,1159],{"class":1146},[1086,31180,1133],{"class":1146},[1086,31182,31183],{"class":1146}," false",[1086,31185,27132],{"class":1146},[1086,31187,31188],{"class":1088,"line":3409},[1086,31189,31190],{"class":1146},"  ],\n",[1086,31192,31193,31195,31197,31199,31201],{"class":1088,"line":3415},[1086,31194,1152],{"class":1146},[1086,31196,30324],{"class":1155},[1086,31198,1159],{"class":1146},[1086,31200,1133],{"class":1146},[1086,31202,6580],{"class":1146},[1086,31204,31205,31207,31209,31211,31213,31215,31217,31219,31221,31223,31225,31227,31229,31231,31233,31235,31237,31239,31241,31243,31246,31248,31250,31253,31255,31257,31259,31262,31264],{"class":1088,"line":3421},[1086,31206,30335],{"class":1146},[1086,31208,1195],{"class":1146},[1086,31210,4184],{"class":1092},[1086,31212,1159],{"class":1146},[1086,31214,1133],{"class":1146},[1086,31216,1195],{"class":1146},[1086,31218,30348],{"class":1096},[1086,31220,1159],{"class":1146},[1086,31222,1227],{"class":1146},[1086,31224,1195],{"class":1146},[1086,31226,29437],{"class":1092},[1086,31228,1159],{"class":1146},[1086,31230,1133],{"class":1146},[1086,31232,31116],{"class":1187},[1086,31234,1227],{"class":1146},[1086,31236,1195],{"class":1146},[1086,31238,29457],{"class":1092},[1086,31240,1159],{"class":1146},[1086,31242,1133],{"class":1146},[1086,31244,31245],{"class":1187}," 15.0",[1086,31247,1227],{"class":1146},[1086,31249,1195],{"class":1146},[1086,31251,31252],{"class":1092},"color",[1086,31254,1159],{"class":1146},[1086,31256,1133],{"class":1146},[1086,31258,1195],{"class":1146},[1086,31260,31261],{"class":1096},"#1E3A5F",[1086,31263,1159],{"class":1146},[1086,31265,4792],{"class":1146},[1086,31267,31268,31270,31272,31274,31276,31278,31280,31282,31284,31286,31288,31290,31292,31294,31296,31298,31300,31302,31304,31306,31309,31311,31313,31315,31317,31319,31321,31324,31326],{"class":1088,"line":3427},[1086,31269,30335],{"class":1146},[1086,31271,1195],{"class":1146},[1086,31273,4184],{"class":1092},[1086,31275,1159],{"class":1146},[1086,31277,1133],{"class":1146},[1086,31279,1195],{"class":1146},[1086,31281,30382],{"class":1096},[1086,31283,1159],{"class":1146},[1086,31285,1227],{"class":1146},[1086,31287,1195],{"class":1146},[1086,31289,29437],{"class":1092},[1086,31291,1159],{"class":1146},[1086,31293,1133],{"class":1146},[1086,31295,31245],{"class":1187},[1086,31297,1227],{"class":1146},[1086,31299,1195],{"class":1146},[1086,31301,29457],{"class":1092},[1086,31303,1159],{"class":1146},[1086,31305,1133],{"class":1146},[1086,31307,31308],{"class":1187}," 30.0",[1086,31310,1227],{"class":1146},[1086,31312,1195],{"class":1146},[1086,31314,31252],{"class":1092},[1086,31316,1159],{"class":1146},[1086,31318,1133],{"class":1146},[1086,31320,1195],{"class":1146},[1086,31322,31323],{"class":1096},"#3D1A5F",[1086,31325,1159],{"class":1146},[1086,31327,27132],{"class":1146},[1086,31329,31330],{"class":1088,"line":3433},[1086,31331,9465],{"class":1146},[1086,31333,31334],{"class":1088,"line":3439},[1086,31335,1291],{"class":1146},[842,31337,31338],{},"This powers interactive, beat-synced waveform players in the browser.",[1045,31340,31342],{"className":31341},[13033,13034,1052],[31343,31344,31345,31346],"video",{"controls":738,"width":30896},"\n  ",[27530,31347],{"src":31348,"type":31349},"/videos/blog/musictechlab_blog_ableton_analyser_wavesurfer.mp4","video/mp4",[1074,31351,31353],{"id":31352},"live-demo","Live Demo",[842,31355,31356,7826,31359],{},[996,31357,31358],{},"Try it yourself:",[846,31360,31361],{"href":31361,"rel":31362},"https://mtl-ableton-analyser.web.app",[850],[842,31364,31365],{},"The web app lets you:",[958,31367,31368,31371,31377,31380,31383],{},[961,31369,31370],{},"Load audio files (MP3, WAV) directly in the browser",[961,31372,31373,31374,31376],{},"Parse ",[895,31375,30100],{}," files client-side – no backend required",[961,31378,31379],{},"View waveform with beat grid and section overlays",[961,31381,31382],{},"Toggle between bars and time-based timelines",[961,31384,31385],{},"Navigate by clicking on sections or the waveform",[863,31387,31389],{"id":31388},"using-mtl-ableton-analyser","Using MTL Ableton Analyser",[842,31391,31392],{},"The tool is available as an open-source Python CLI:",[1013,31394,31396],{"className":1080,"code":31395,"filename":8510,"language":1082,"meta":728,"style":728},"git clone https://github.com/musictechlab/mtl-ableton-analyser\ncd mtl-ableton-analyser\npoetry install\n",[895,31397,31398,31407,31414],{"__ignoreMap":728},[1086,31399,31400,31402,31404],{"class":1088,"line":1089},[1086,31401,1093],{"class":1092},[1086,31403,1097],{"class":1096},[1086,31405,31406],{"class":1096}," https://github.com/musictechlab/mtl-ableton-analyser\n",[1086,31408,31409,31411],{"class":1088,"line":729},[1086,31410,1106],{"class":1105},[1086,31412,31413],{"class":1096}," mtl-ableton-analyser\n",[1086,31415,31416,31418],{"class":1088,"line":1112},[1086,31417,1115],{"class":1092},[1086,31419,1118],{"class":1096},[1074,31421,31423],{"id":31422},"analyze-files","Analyze Files",[1013,31425,31428],{"className":1080,"code":31426,"filename":31427,"language":1082,"meta":728,"style":728},"poetry run mtl-ableton-analyser analyze /path/to/project.als --verbose\npoetry run mtl-ableton-analyser analyze /path/to/audio.mp3.asd\n","Single File Analysis",[895,31429,31430,31448],{"__ignoreMap":728},[1086,31431,31432,31434,31436,31439,31442,31445],{"class":1088,"line":1089},[1086,31433,1115],{"class":1092},[1086,31435,11970],{"class":1096},[1086,31437,31438],{"class":1096}," mtl-ableton-analyser",[1086,31440,31441],{"class":1096}," analyze",[1086,31443,31444],{"class":1096}," /path/to/project.als",[1086,31446,31447],{"class":1096}," --verbose\n",[1086,31449,31450,31452,31454,31456,31458],{"class":1088,"line":729},[1086,31451,1115],{"class":1092},[1086,31453,11970],{"class":1096},[1086,31455,31438],{"class":1096},[1086,31457,31441],{"class":1096},[1086,31459,31460],{"class":1096}," /path/to/audio.mp3.asd\n",[1013,31462,31465],{"className":1080,"code":31463,"filename":31464,"language":1082,"meta":728,"style":728},"poetry run mtl-ableton-analyser analyze /path/to/projects --recursive\n","Directory Scanning",[895,31466,31467],{"__ignoreMap":728},[1086,31468,31469,31471,31473,31475,31477,31480],{"class":1088,"line":1089},[1086,31470,1115],{"class":1092},[1086,31472,11970],{"class":1096},[1086,31474,31438],{"class":1096},[1086,31476,31441],{"class":1096},[1086,31478,31479],{"class":1096}," /path/to/projects",[1086,31481,31482],{"class":1096}," --recursive\n",[1074,31484,31486],{"id":31485},"generate-visualizations","Generate Visualizations",[1013,31488,31491],{"className":1080,"code":31489,"filename":31490,"language":1082,"meta":728,"style":728},"poetry run mtl-ableton-analyser visualize audio.mp3 --als project.als -o waveform.png\n","Waveform with Sections",[895,31492,31493],{"__ignoreMap":728},[1086,31494,31495,31497,31499,31501,31504,31507,31510,31513,31516],{"class":1088,"line":1089},[1086,31496,1115],{"class":1092},[1086,31498,11970],{"class":1096},[1086,31500,31438],{"class":1096},[1086,31502,31503],{"class":1096}," visualize",[1086,31505,31506],{"class":1096}," audio.mp3",[1086,31508,31509],{"class":1096}," --als",[1086,31511,31512],{"class":1096}," project.als",[1086,31514,31515],{"class":1096}," -o",[1086,31517,31518],{"class":1096}," waveform.png\n",[1074,31520,31522],{"id":31521},"export-for-web","Export for Web",[1013,31524,31527],{"className":1080,"code":31525,"filename":31526,"language":1082,"meta":728,"style":728},"poetry run mtl-ableton-analyser export audio.mp3 --als project.als -o waveform.json\n","WaveSurfer JSON Export",[895,31528,31529],{"__ignoreMap":728},[1086,31530,31531,31533,31535,31537,31540,31542,31544,31546,31548],{"class":1088,"line":1089},[1086,31532,1115],{"class":1092},[1086,31534,11970],{"class":1096},[1086,31536,31438],{"class":1096},[1086,31538,31539],{"class":1096}," export",[1086,31541,31506],{"class":1096},[1086,31543,31509],{"class":1096},[1086,31545,31512],{"class":1096},[1086,31547,31515],{"class":1096},[1086,31549,31550],{"class":1096}," waveform.json\n",[863,31552,31554],{"id":31553},"capabilities-summary","Capabilities Summary",[871,31556,31557,31570],{},[874,31558,31559],{},[877,31560,31561,31564,31566,31568],{},[880,31562,31563],{},"Capability",[880,31565,30100],{},[880,31567,30104],{},[880,31569,2719],{},[887,31571,31572,31586,31598,31610,31621,31633,31645],{},[877,31573,31574,31577,31580,31583],{},[892,31575,31576],{},"Extract BPM",[892,31578,31579],{},"✅ Exact",[892,31581,31582],{},"✅ Detected",[892,31584,31585],{},".als is authoritative",[877,31587,31588,31590,31593,31595],{},[892,31589,30231],{},[892,31591,31592],{},"✅",[892,31594,31592],{},[892,31596,31597],{},"Both reliable",[877,31599,31600,31602,31604,31607],{},[892,31601,30261],{},[892,31603,31592],{},[892,31605,31606],{},"❌",[892,31608,31609],{},"Only in project files",[877,31611,31612,31614,31616,31618],{},[892,31613,30277],{},[892,31615,31592],{},[892,31617,31606],{},[892,31619,31620],{},"Beat-to-time mapping",[877,31622,31623,31626,31628,31630],{},[892,31624,31625],{},"Section Markers",[892,31627,31592],{},[892,31629,31606],{},[892,31631,31632],{},"From locators",[877,31634,31635,31638,31640,31642],{},[892,31636,31637],{},"Waveform Presence",[892,31639,31606],{},[892,31641,31592],{},[892,31643,31644],{},"Cache indicator",[877,31646,31647,31649,31651,31653],{},[892,31648,14144],{},[892,31650,31592],{},[892,31652,31592],{},[892,31654,31655],{},"Basic metadata",[863,31657,31659],{"id":31658},"limitations","Limitations",[842,31661,31662],{},"While we can extract a lot, some data remains inaccessible:",[958,31664,31665,31671,31679,31685],{},[961,31666,31667,31670],{},[996,31668,31669],{},"Detailed onset data"," – Ableton stores transient detection but in an undocumented format",[961,31672,31673,31676,31677],{},[996,31674,31675],{},"Embedded waveform peaks"," – Detected but not directly extractable from ",[895,31678,30104],{},[961,31680,31681,31684],{},[996,31682,31683],{},"Plugin states"," – Complex binary data within the XML",[961,31686,31687,31690],{},[996,31688,31689],{},"Write capability"," – This is a read-only tool",[863,31692,31693],{"id":16446},"What's Next?",[842,31695,31696],{},"This tool is part of our broader work on music analysis at MusicTech Lab. Combined with our other Ableton integrations, you can build powerful workflows:",[991,31698,31699,31705,31711,31717],{},[961,31700,31701,31704],{},[996,31702,31703],{},"Create section markers in Ableton"," → Export with our Max for Live device",[961,31706,31707,31710],{},[996,31708,31709],{},"Analyze files programmatically"," → Use MTL Ableton Analyser",[961,31712,31713,31716],{},[996,31714,31715],{},"Visualize in web applications"," → WaveSurfer.js integration",[961,31718,31719,31722],{},[996,31720,31721],{},"Enhance with AI"," → Feed structure data to ML models",[4937,31724],{},[863,31726,27869],{"id":27868},[958,31728,31729,31734,31739,31745],{},[961,31730,31731,31733],{},[846,31732,27886],{"href":459}," – Create and export section markers directly from Ableton",[961,31735,31736,31738],{},[846,31737,406],{"href":407}," – Send Ableton data to web services",[961,31740,31741,31744],{},[846,31742,31743],{"href":101},"Automatic Song Structure Analysis – How AI Detects Intro, Verse, and Chorus"," – AI-powered section detection",[961,31746,31747,31750],{},[846,31748,31749],{"href":395},"Building a DIY MIDI Controller for Ableton Live with Arduino"," – Hardware integration with Ableton",[4937,31752],{},[1032,31754,31755],{},[842,31756,31757,31760,31761,31765],{},[996,31758,31759],{},"Open Source:"," MTL Ableton Analyser is available on ",[846,31762,15320],{"href":31763,"rel":31764},"https://github.com/musictechlab/mtl-ableton-analyser",[850],". Contributions welcome!",[1680,31767,31768],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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}",{"title":728,"searchDepth":729,"depth":729,"links":31770},[31771,31772,31777,31781,31786,31791,31792,31793,31794],{"id":30111,"depth":729,"text":30112},{"id":30182,"depth":729,"text":30183,"children":31773},[31774,31775,31776],{"id":30192,"depth":1112,"text":30193},{"id":30276,"depth":1112,"text":30277},{"id":30300,"depth":1112,"text":30301},{"id":30523,"depth":729,"text":30524,"children":31778},[31779,31780],{"id":30532,"depth":1112,"text":30533},{"id":30601,"depth":1112,"text":30602},{"id":30879,"depth":729,"text":30880,"children":31782},[31783,31784,31785],{"id":30883,"depth":1112,"text":30884},{"id":27387,"depth":1112,"text":27388},{"id":31352,"depth":1112,"text":31353},{"id":31388,"depth":729,"text":31389,"children":31787},[31788,31789,31790],{"id":31422,"depth":1112,"text":31423},{"id":31485,"depth":1112,"text":31486},{"id":31521,"depth":1112,"text":31522},{"id":31553,"depth":729,"text":31554},{"id":31658,"depth":729,"text":31659},{"id":16446,"depth":729,"text":31693},{"id":27868,"depth":729,"text":27869},"2026-01-31T00:00:00.000Z","A deep dive into parsing Ableton Live project files. Learn what metadata hides inside .als and .asd files and how to extract it with Python.",{"src":31798},"/images/blog/musictechlab_blog_ableton_als_asd_extraction.webp",{"enabled":738,"items":31800},[31801,31804,31806,31809],{"text":31802,"icon":31803},"Ableton .als files are gzip-compressed XML, making them straightforward to parse with Python.","i-lucide-file-code",{"text":31805,"icon":9547},"Locators from .als files provide a complete song structure map with precise beat positions.",{"text":31807,"icon":31808},"BPM in .asd files is auto-detected and may differ from the authoritative .als project tempo.","i-lucide-timer",{"text":31810,"icon":3844},"Output exports to JSON compatible with WaveSurfer.js for interactive web-based visualization.",{},{"title":136,"description":31796},[26062,18784,3210,27927],"sP2M7DpVovlE_0fKXV3tAiegedrHDDXqdc2vqO4Pqh8",{"id":31816,"title":100,"authors":31817,"badge":31820,"body":31821,"category":731,"client":723,"date":35157,"description":35158,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":35159,"keyTakeaways":35160,"meta":35170,"navigation":738,"path":101,"seo":35171,"status":723,"stem":102,"tags":35172,"teaser":723,"__hash__":35173},"posts/blog/music-data/automatic-song-structure-analysis-how-ai-detects-intro-verse-chorus.md",[31818],{"name":834,"to":720,"avatar":31819},{"src":722},{"label":837,"color":838},{"type":725,"value":31822,"toc":35119},[31823,31834,31837,31841,31844,31908,31911,31915,31918,31991,31995,32002,32021,32147,32155,32159,32166,32254,32258,32261,32303,32306,32320,32324,32327,32455,32458,32461,32464,32824,32833,32837,32839,32867,32871,32888,32910,32913,32919,32923,32926,32941,32944,33031,33034,33073,33076,33172,33176,33424,33428,33431,33574,33577,33609,33611,33615,33618,33914,33918,33921,33938,33942,33945,33963,33967,33970,34038,34041,34059,34063,34067,34070,34076,34131,34141,34181,34185,34188,34354,34463,34467,34551,34563,34565,34568,34604,34606,34610,34613,34617,34710,34714,34778,34782,34789,34857,34869,34873,34876,34896,34899,34903,34910,34976,34981,35026,35029,35033,35081,35083,35085,35104,35106,35116],[842,31824,31825,31826,31829,31830,31833],{},"When working with music at scale – whether for DJ software, music education, or content creation – one of the most valuable pieces of metadata is the ",[996,31827,31828],{},"song structure",". Where does the chorus start? How long is the intro? When does the bridge appear? Traditionally, this information required manual tagging or expensive commercial solutions. With ",[996,31831,31832],{},"MTL Audio Locators",", we've built an open-source tool that detects song sections automatically using AI and signal processing.",[842,31835,31836],{},"In this article, we'll explore how automatic structure analysis works and how you can use it in your projects.",[863,31838,31840],{"id":31839},"the-challenge-why-song-structure-matters","The Challenge: Why Song Structure Matters",[842,31842,31843],{},"Song structure – the arrangement of sections like INTRO, VERSE, CHORUS, BRIDGE, and OUTRO – is fundamental to how we experience music. For technical applications, this data enables:",[871,31845,31846,31856],{},[874,31847,31848],{},[877,31849,31850,31853],{},[880,31851,31852],{},"Use Case",[880,31854,31855],{},"Application",[887,31857,31858,31868,31878,31888,31898],{},[877,31859,31860,31865],{},[892,31861,31862],{},[996,31863,31864],{},"DJ Software",[892,31866,31867],{},"Auto-sync to chorus, smart mixing",[877,31869,31870,31875],{},[892,31871,31872],{},[996,31873,31874],{},"Music Education",[892,31876,31877],{},"Visual learning aids, section practice",[877,31879,31880,31885],{},[892,31881,31882],{},[996,31883,31884],{},"Content Creation",[892,31886,31887],{},"Quick navigation, highlight extraction",[877,31889,31890,31895],{},[892,31891,31892],{},[996,31893,31894],{},"DAW Integration",[892,31896,31897],{},"Import markers for editing",[877,31899,31900,31905],{},[892,31901,31902],{},[996,31903,31904],{},"Recommendation Systems",[892,31906,31907],{},"Structure-aware similarity matching",[842,31909,31910],{},"The challenge is that this information rarely exists in metadata. Even when producers add markers in their DAW, these don't export with the audio file.",[863,31912,31914],{"id":31913},"available-analysis-engines","Available Analysis Engines",[842,31916,31917],{},"MTL Audio Locators provides three analysis backends, automatically selecting the best available option:",[871,31919,31920,31936],{},[874,31921,31922],{},[877,31923,31924,31927,31929,31932,31934],{},[880,31925,31926],{},"Engine",[880,31928,8043],{},[880,31930,31931],{},"Accuracy",[880,31933,28277],{},[880,31935,31852],{},[887,31937,31938,31956,31973],{},[877,31939,31940,31945,31948,31950,31953],{},[892,31941,31942],{},[996,31943,31944],{},"allin1",[892,31946,31947],{},"Deep Learning",[892,31949,14553],{},[892,31951,31952],{},"Slower",[892,31954,31955],{},"Production-quality analysis",[877,31957,31958,31963,31966,31968,31970],{},[892,31959,31960],{},[996,31961,31962],{},"MSAF",[892,31964,31965],{},"Traditional MIR",[892,31967,2126],{},[892,31969,2126],{},[892,31971,31972],{},"Research, comparison",[877,31974,31975,31980,31983,31986,31988],{},[892,31976,31977],{},[996,31978,31979],{},"librosa",[892,31981,31982],{},"Spectral",[892,31984,31985],{},"Basic",[892,31987,28310],{},[892,31989,31990],{},"Fallback, quick scans",[1074,31992,31994],{"id":31993},"allin1-ai-powered-analysis","allin1 – AI-Powered Analysis",[842,31996,5119,31997,32001],{},[846,31998,31944],{"href":31999,"rel":32000},"https://github.com/mir-aidj/all-in-one",[850]," model represents the state-of-the-art in music structure analysis. It's a deep learning model trained specifically on song segmentation tasks. The analysis pipeline:",[991,32003,32004,32009,32015],{},[961,32005,32006,32008],{},[996,32007,27836],{}," – Isolates vocals, drums, bass, and other instruments using Demucs",[961,32010,32011,32014],{},[996,32012,32013],{},"Spectrogram extraction"," – Converts each stem to time-frequency representation",[961,32016,32017,32020],{},[996,32018,32019],{},"Structure prediction"," – Neural network predicts section boundaries and labels",[1013,32022,32024],{"className":1368,"code":32023,"language":1250,"meta":728,"style":728},"# Using allin1 directly\nimport allin1\n\nresult = allin1.analyze(\"track.mp3\")\nprint(f\"BPM: {result.bpm}\")\nfor segment in result.segments:\n    print(f\"{segment.start:.2f}s - {segment.label}\")\n",[895,32025,32026,32031,32038,32042,32066,32091,32107],{"__ignoreMap":728},[1086,32027,32028],{"class":1088,"line":1089},[1086,32029,32030],{"class":1427},"# Using allin1 directly\n",[1086,32032,32033,32035],{"class":1088,"line":729},[1086,32034,6503],{"class":1423},[1086,32036,32037],{"class":1436}," allin1\n",[1086,32039,32040],{"class":1088,"line":1112},[1086,32041,3390],{"emptyLinePlaceholder":738},[1086,32043,32044,32046,32048,32051,32053,32056,32058,32060,32062,32064],{"class":1088,"line":1181},[1086,32045,29358],{"class":1436},[1086,32047,1440],{"class":1146},[1086,32049,32050],{"class":1436}," allin1",[1086,32052,861],{"class":1146},[1086,32054,32055],{"class":1105},"analyze",[1086,32057,1398],{"class":1146},[1086,32059,1159],{"class":1146},[1086,32061,30973],{"class":1096},[1086,32063,1159],{"class":1146},[1086,32065,1455],{"class":1146},[1086,32067,32068,32070,32072,32074,32077,32079,32081,32083,32085,32087,32089],{"class":1088,"line":1205},[1086,32069,10725],{"class":1105},[1086,32071,1398],{"class":1146},[1086,32073,5962],{"class":1155},[1086,32075,32076],{"class":1096},"\"BPM: ",[1086,32078,4409],{"class":1187},[1086,32080,1473],{"class":1105},[1086,32082,861],{"class":1146},[1086,32084,31015],{"class":4109},[1086,32086,4423],{"class":1187},[1086,32088,1159],{"class":1096},[1086,32090,1455],{"class":1146},[1086,32092,32093,32095,32097,32099,32101,32103,32105],{"class":1088,"line":1276},[1086,32094,10799],{"class":1423},[1086,32096,29405],{"class":1436},[1086,32098,5931],{"class":1423},[1086,32100,23102],{"class":1436},[1086,32102,861],{"class":1146},[1086,32104,29416],{"class":4109},[1086,32106,1418],{"class":1146},[1086,32108,32109,32111,32113,32115,32117,32119,32122,32124,32126,32129,32131,32133,32135,32137,32139,32141,32143,32145],{"class":1088,"line":1282},[1086,32110,11068],{"class":1105},[1086,32112,1398],{"class":1146},[1086,32114,5962],{"class":1155},[1086,32116,1159],{"class":1096},[1086,32118,4409],{"class":1187},[1086,32120,32121],{"class":1105},"segment",[1086,32123,861],{"class":1146},[1086,32125,29437],{"class":4109},[1086,32127,32128],{"class":1155},":.2f",[1086,32130,4423],{"class":1187},[1086,32132,29507],{"class":1096},[1086,32134,4409],{"class":1187},[1086,32136,32121],{"class":1105},[1086,32138,861],{"class":1146},[1086,32140,10840],{"class":4109},[1086,32142,4423],{"class":1187},[1086,32144,1159],{"class":1096},[1086,32146,1455],{"class":1146},[1572,32148,32149],{},[842,32150,32151,32154],{},[996,32152,32153],{},"First Run:"," allin1 downloads ~1.5GB of models on first use (Demucs for demixing + Harmonix for structure).",[1074,32156,32158],{"id":32157},"msaf-music-structure-analysis-framework","MSAF – Music Structure Analysis Framework",[842,32160,32161,32165],{},[846,32162,31962],{"href":32163,"rel":32164},"https://github.com/urinieto/msaf",[850]," provides traditional Music Information Retrieval (MIR) algorithms. We use spectral flux for boundary detection combined with Fourier Magnitude Coefficients for labeling:",[1013,32167,32169],{"className":1368,"code":32168,"language":1250,"meta":728,"style":728},"import msaf\n\nboundaries, labels = msaf.process(\n    \"track.mp3\",\n    boundaries_id='sf',   # Spectral flux\n    labels_id='fmc2d'     # 2D Fourier Magnitude Coefficients\n)\n",[895,32170,32171,32178,32182,32204,32214,32233,32250],{"__ignoreMap":728},[1086,32172,32173,32175],{"class":1088,"line":1089},[1086,32174,6503],{"class":1423},[1086,32176,32177],{"class":1436}," msaf\n",[1086,32179,32180],{"class":1088,"line":729},[1086,32181,3390],{"emptyLinePlaceholder":738},[1086,32183,32184,32187,32189,32192,32194,32197,32199,32202],{"class":1088,"line":1112},[1086,32185,32186],{"class":1436},"boundaries",[1086,32188,1227],{"class":1146},[1086,32190,32191],{"class":1436}," labels ",[1086,32193,1440],{"class":1146},[1086,32195,32196],{"class":1436}," msaf",[1086,32198,861],{"class":1146},[1086,32200,32201],{"class":1105},"process",[1086,32203,4094],{"class":1146},[1086,32205,32206,32208,32210,32212],{"class":1088,"line":1181},[1086,32207,1169],{"class":1146},[1086,32209,30973],{"class":1096},[1086,32211,1159],{"class":1146},[1086,32213,1202],{"class":1146},[1086,32215,32216,32219,32221,32223,32226,32228,32230],{"class":1088,"line":1205},[1086,32217,32218],{"class":1401},"    boundaries_id",[1086,32220,1440],{"class":1146},[1086,32222,10742],{"class":1146},[1086,32224,32225],{"class":1096},"sf",[1086,32227,10742],{"class":1146},[1086,32229,1227],{"class":1146},[1086,32231,32232],{"class":1427},"   # Spectral flux\n",[1086,32234,32235,32238,32240,32242,32245,32247],{"class":1088,"line":1276},[1086,32236,32237],{"class":1401},"    labels_id",[1086,32239,1440],{"class":1146},[1086,32241,10742],{"class":1146},[1086,32243,32244],{"class":1096},"fmc2d",[1086,32246,10742],{"class":1146},[1086,32248,32249],{"class":1427},"     # 2D Fourier Magnitude Coefficients\n",[1086,32251,32252],{"class":1088,"line":1282},[1086,32253,1455],{"class":1146},[1074,32255,32257],{"id":32256},"librosa-spectral-fallback","librosa – Spectral Fallback",[842,32259,32260],{},"When neither allin1 nor MSAF is available, we use a custom algorithm based on librosa that combines multiple features:",[871,32262,32263,32271],{},[874,32264,32265],{},[877,32266,32267,32269],{},[880,32268,1974],{},[880,32270,3021],{},[887,32272,32273,32283,32293],{},[877,32274,32275,32280],{},[892,32276,32277],{},[996,32278,32279],{},"Chroma CQT",[892,32281,32282],{},"Harmonic content (chord changes)",[877,32284,32285,32290],{},[892,32286,32287],{},[996,32288,32289],{},"MFCC",[892,32291,32292],{},"Timbral characteristics (texture changes)",[877,32294,32295,32300],{},[892,32296,32297],{},[996,32298,32299],{},"Spectral Contrast",[892,32301,32302],{},"Brightness/darkness patterns",[842,32304,32305],{},"The algorithm:",[991,32307,32308,32311,32314,32317],{},[961,32309,32310],{},"Extracts all features and stacks them",[961,32312,32313],{},"Uses agglomerative clustering to find segment boundaries",[961,32315,32316],{},"Snaps boundaries to nearest beats for musical alignment",[961,32318,32319],{},"Labels sections using position heuristics and K-means clustering",[863,32321,32323],{"id":32322},"section-labeling-convention","Section Labeling Convention",[842,32325,32326],{},"All backends output sections with a consistent naming convention designed for DAW compatibility:",[871,32328,32329,32344],{},[874,32330,32331],{},[877,32332,32333,32336,32339,32342],{},[880,32334,32335],{},"Section",[880,32337,32338],{},"Prefix",[880,32340,32341],{},"Color",[880,32343,7741],{},[887,32345,32346,32359,32373,32387,32401,32415,32428,32442],{},[877,32347,32348,32351,32354,32357],{},[892,32349,32350],{},"Intro",[892,32352,32353],{},"INT",[892,32355,32356],{},"#808080",[892,32358,30348],{},[877,32360,32361,32364,32367,32370],{},[892,32362,32363],{},"Verse",[892,32365,32366],{},"VRS",[892,32368,32369],{},"#4CAF50",[892,32371,32372],{},"VRS1, VRS2",[877,32374,32375,32378,32381,32384],{},[892,32376,32377],{},"Pre-Chorus",[892,32379,32380],{},"PRE",[892,32382,32383],{},"#8BC34A",[892,32385,32386],{},"PRE1",[877,32388,32389,32392,32395,32398],{},[892,32390,32391],{},"Chorus",[892,32393,32394],{},"CHO",[892,32396,32397],{},"#FF5722",[892,32399,32400],{},"CHO1, CHO2",[877,32402,32403,32406,32409,32412],{},[892,32404,32405],{},"Bridge",[892,32407,32408],{},"BRG",[892,32410,32411],{},"#00BCD4",[892,32413,32414],{},"BRG1",[877,32416,32417,32420,32423,32426],{},[892,32418,32419],{},"Breakdown",[892,32421,32422],{},"BRD",[892,32424,32425],{},"#2196F3",[892,32427,30450],{},[877,32429,32430,32433,32436,32439],{},[892,32431,32432],{},"Build",[892,32434,32435],{},"BUI",[892,32437,32438],{},"#9C27B0",[892,32440,32441],{},"BUI1",[877,32443,32444,32447,32450,32453],{},[892,32445,32446],{},"Outro",[892,32448,32449],{},"OUT",[892,32451,32452],{},"#607D8B",[892,32454,30484],{},[842,32456,32457],{},"The numbered suffix (VRS1, VRS2) indicates occurrence order – useful for identifying repeating sections.",[863,32459,32460],{"id":16139},"Output Format",[842,32462,32463],{},"Analysis results follow a JSON schema designed for easy integration:",[1013,32465,32467],{"className":1136,"code":32466,"language":1139,"meta":728,"style":728},"{\n  \"track_id\": \"my_song\",\n  \"bpm\": 128,\n  \"sections\": [\n    {\"label\": \"INT1\", \"time_s\": 0.0, \"color\": \"#808080\"},\n    {\"label\": \"VRS1\", \"time_s\": 15.5, \"color\": \"#4CAF50\"},\n    {\"label\": \"CHO1\", \"time_s\": 45.2, \"color\": \"#FF5722\"},\n    {\"label\": \"VRS2\", \"time_s\": 75.0, \"color\": \"#4CAF50\"},\n    {\"label\": \"CHO2\", \"time_s\": 105.3, \"color\": \"#FF5722\"},\n    {\"label\": \"OUT1\", \"time_s\": 135.8, \"color\": \"#607D8B\"}\n  ]\n}\n",[895,32468,32469,32473,32493,32507,32519,32568,32618,32667,32717,32767,32816,32820],{"__ignoreMap":728},[1086,32470,32471],{"class":1088,"line":1089},[1086,32472,1147],{"class":1146},[1086,32474,32475,32477,32480,32482,32484,32486,32489,32491],{"class":1088,"line":729},[1086,32476,1152],{"class":1146},[1086,32478,32479],{"class":1155},"track_id",[1086,32481,1159],{"class":1146},[1086,32483,1133],{"class":1146},[1086,32485,1195],{"class":1146},[1086,32487,32488],{"class":1096},"my_song",[1086,32490,1159],{"class":1146},[1086,32492,1202],{"class":1146},[1086,32494,32495,32497,32499,32501,32503,32505],{"class":1088,"line":1112},[1086,32496,1152],{"class":1146},[1086,32498,31015],{"class":1155},[1086,32500,1159],{"class":1146},[1086,32502,1133],{"class":1146},[1086,32504,9016],{"class":1187},[1086,32506,1202],{"class":1146},[1086,32508,32509,32511,32513,32515,32517],{"class":1088,"line":1181},[1086,32510,1152],{"class":1146},[1086,32512,30324],{"class":1155},[1086,32514,1159],{"class":1146},[1086,32516,1133],{"class":1146},[1086,32518,6580],{"class":1146},[1086,32520,32521,32523,32525,32527,32529,32531,32533,32535,32537,32539,32541,32544,32546,32548,32550,32552,32554,32556,32558,32560,32562,32564,32566],{"class":1088,"line":1205},[1086,32522,30335],{"class":1146},[1086,32524,1159],{"class":1146},[1086,32526,10840],{"class":1092},[1086,32528,1159],{"class":1146},[1086,32530,1133],{"class":1146},[1086,32532,1195],{"class":1146},[1086,32534,30348],{"class":1096},[1086,32536,1159],{"class":1146},[1086,32538,1227],{"class":1146},[1086,32540,1195],{"class":1146},[1086,32542,32543],{"class":1092},"time_s",[1086,32545,1159],{"class":1146},[1086,32547,1133],{"class":1146},[1086,32549,31116],{"class":1187},[1086,32551,1227],{"class":1146},[1086,32553,1195],{"class":1146},[1086,32555,31252],{"class":1092},[1086,32557,1159],{"class":1146},[1086,32559,1133],{"class":1146},[1086,32561,1195],{"class":1146},[1086,32563,32356],{"class":1096},[1086,32565,1159],{"class":1146},[1086,32567,17375],{"class":1146},[1086,32569,32570,32572,32574,32576,32578,32580,32582,32585,32587,32589,32591,32593,32595,32597,32600,32602,32604,32606,32608,32610,32612,32614,32616],{"class":1088,"line":1276},[1086,32571,30335],{"class":1146},[1086,32573,1159],{"class":1146},[1086,32575,10840],{"class":1092},[1086,32577,1159],{"class":1146},[1086,32579,1133],{"class":1146},[1086,32581,1195],{"class":1146},[1086,32583,32584],{"class":1096},"VRS1",[1086,32586,1159],{"class":1146},[1086,32588,1227],{"class":1146},[1086,32590,1195],{"class":1146},[1086,32592,32543],{"class":1092},[1086,32594,1159],{"class":1146},[1086,32596,1133],{"class":1146},[1086,32598,32599],{"class":1187}," 15.5",[1086,32601,1227],{"class":1146},[1086,32603,1195],{"class":1146},[1086,32605,31252],{"class":1092},[1086,32607,1159],{"class":1146},[1086,32609,1133],{"class":1146},[1086,32611,1195],{"class":1146},[1086,32613,32369],{"class":1096},[1086,32615,1159],{"class":1146},[1086,32617,17375],{"class":1146},[1086,32619,32620,32622,32624,32626,32628,32630,32632,32634,32636,32638,32640,32642,32644,32646,32649,32651,32653,32655,32657,32659,32661,32663,32665],{"class":1088,"line":1282},[1086,32621,30335],{"class":1146},[1086,32623,1159],{"class":1146},[1086,32625,10840],{"class":1092},[1086,32627,1159],{"class":1146},[1086,32629,1133],{"class":1146},[1086,32631,1195],{"class":1146},[1086,32633,30416],{"class":1096},[1086,32635,1159],{"class":1146},[1086,32637,1227],{"class":1146},[1086,32639,1195],{"class":1146},[1086,32641,32543],{"class":1092},[1086,32643,1159],{"class":1146},[1086,32645,1133],{"class":1146},[1086,32647,32648],{"class":1187}," 45.2",[1086,32650,1227],{"class":1146},[1086,32652,1195],{"class":1146},[1086,32654,31252],{"class":1092},[1086,32656,1159],{"class":1146},[1086,32658,1133],{"class":1146},[1086,32660,1195],{"class":1146},[1086,32662,32397],{"class":1096},[1086,32664,1159],{"class":1146},[1086,32666,17375],{"class":1146},[1086,32668,32669,32671,32673,32675,32677,32679,32681,32684,32686,32688,32690,32692,32694,32696,32699,32701,32703,32705,32707,32709,32711,32713,32715],{"class":1088,"line":1288},[1086,32670,30335],{"class":1146},[1086,32672,1159],{"class":1146},[1086,32674,10840],{"class":1092},[1086,32676,1159],{"class":1146},[1086,32678,1133],{"class":1146},[1086,32680,1195],{"class":1146},[1086,32682,32683],{"class":1096},"VRS2",[1086,32685,1159],{"class":1146},[1086,32687,1227],{"class":1146},[1086,32689,1195],{"class":1146},[1086,32691,32543],{"class":1092},[1086,32693,1159],{"class":1146},[1086,32695,1133],{"class":1146},[1086,32697,32698],{"class":1187}," 75.0",[1086,32700,1227],{"class":1146},[1086,32702,1195],{"class":1146},[1086,32704,31252],{"class":1092},[1086,32706,1159],{"class":1146},[1086,32708,1133],{"class":1146},[1086,32710,1195],{"class":1146},[1086,32712,32369],{"class":1096},[1086,32714,1159],{"class":1146},[1086,32716,17375],{"class":1146},[1086,32718,32719,32721,32723,32725,32727,32729,32731,32734,32736,32738,32740,32742,32744,32746,32749,32751,32753,32755,32757,32759,32761,32763,32765],{"class":1088,"line":2685},[1086,32720,30335],{"class":1146},[1086,32722,1159],{"class":1146},[1086,32724,10840],{"class":1092},[1086,32726,1159],{"class":1146},[1086,32728,1133],{"class":1146},[1086,32730,1195],{"class":1146},[1086,32732,32733],{"class":1096},"CHO2",[1086,32735,1159],{"class":1146},[1086,32737,1227],{"class":1146},[1086,32739,1195],{"class":1146},[1086,32741,32543],{"class":1092},[1086,32743,1159],{"class":1146},[1086,32745,1133],{"class":1146},[1086,32747,32748],{"class":1187}," 105.3",[1086,32750,1227],{"class":1146},[1086,32752,1195],{"class":1146},[1086,32754,31252],{"class":1092},[1086,32756,1159],{"class":1146},[1086,32758,1133],{"class":1146},[1086,32760,1195],{"class":1146},[1086,32762,32397],{"class":1096},[1086,32764,1159],{"class":1146},[1086,32766,17375],{"class":1146},[1086,32768,32769,32771,32773,32775,32777,32779,32781,32783,32785,32787,32789,32791,32793,32795,32798,32800,32802,32804,32806,32808,32810,32812,32814],{"class":1088,"line":2700},[1086,32770,30335],{"class":1146},[1086,32772,1159],{"class":1146},[1086,32774,10840],{"class":1092},[1086,32776,1159],{"class":1146},[1086,32778,1133],{"class":1146},[1086,32780,1195],{"class":1146},[1086,32782,30484],{"class":1096},[1086,32784,1159],{"class":1146},[1086,32786,1227],{"class":1146},[1086,32788,1195],{"class":1146},[1086,32790,32543],{"class":1092},[1086,32792,1159],{"class":1146},[1086,32794,1133],{"class":1146},[1086,32796,32797],{"class":1187}," 135.8",[1086,32799,1227],{"class":1146},[1086,32801,1195],{"class":1146},[1086,32803,31252],{"class":1092},[1086,32805,1159],{"class":1146},[1086,32807,1133],{"class":1146},[1086,32809,1195],{"class":1146},[1086,32811,32452],{"class":1096},[1086,32813,1159],{"class":1146},[1086,32815,1291],{"class":1146},[1086,32817,32818],{"class":1088,"line":3398},[1086,32819,9465],{"class":1146},[1086,32821,32822],{"class":1088,"line":1715},[1086,32823,1291],{"class":1146},[1045,32825,32827],{"className":32826},[13033,13034,1052],[842,32828,32829],{},[1027,32830],{"alt":32831,"src":32832,"width":1322},"Structure Analysis Output","/images/blog/musictechlab_blog_song-structure-analysis.webp",[863,32834,32836],{"id":32835},"using-mtl-audio-locators","Using MTL Audio Locators",[1074,32838,8510],{"id":8509},[1013,32840,32843],{"className":1080,"code":32841,"filename":32842,"language":1082,"meta":728,"style":728},"git clone https://github.com/musictechlab/mtl-audiolocators\ncd mtl-audiolocators\npoetry install\n","Clone and Install",[895,32844,32845,32854,32861],{"__ignoreMap":728},[1086,32846,32847,32849,32851],{"class":1088,"line":1089},[1086,32848,1093],{"class":1092},[1086,32850,1097],{"class":1096},[1086,32852,32853],{"class":1096}," https://github.com/musictechlab/mtl-audiolocators\n",[1086,32855,32856,32858],{"class":1088,"line":729},[1086,32857,1106],{"class":1105},[1086,32859,32860],{"class":1096}," mtl-audiolocators\n",[1086,32862,32863,32865],{"class":1088,"line":1112},[1086,32864,1115],{"class":1092},[1086,32866,1118],{"class":1096},[1074,32868,32870],{"id":32869},"cli-usage","CLI Usage",[1013,32872,32875],{"className":1080,"code":32873,"filename":32874,"language":1082,"meta":728,"style":728},"poetry run analyze track.mp3\n","Analyze Single Track",[895,32876,32877],{"__ignoreMap":728},[1086,32878,32879,32881,32883,32885],{"class":1088,"line":1089},[1086,32880,1115],{"class":1092},[1086,32882,11970],{"class":1096},[1086,32884,31441],{"class":1096},[1086,32886,32887],{"class":1096}," track.mp3\n",[1013,32889,32892],{"className":1080,"code":32890,"filename":32891,"language":1082,"meta":728,"style":728},"poetry run analyze track.mp3 -o structure.json\n","Custom Output Path",[895,32893,32894],{"__ignoreMap":728},[1086,32895,32896,32898,32900,32902,32905,32907],{"class":1088,"line":1089},[1086,32897,1115],{"class":1092},[1086,32899,11970],{"class":1096},[1086,32901,31441],{"class":1096},[1086,32903,32904],{"class":1096}," track.mp3",[1086,32906,31515],{"class":1096},[1086,32908,32909],{"class":1096}," structure.json\n",[842,32911,32912],{},"Sample CLI output:",[1013,32914,32917],{"className":32915,"code":32916,"language":1018},[1016],"Analyzing: track.mp3\nUsing allin1 (AI-based analysis)\n\n========================================\nTrack: track\nBPM: 128\nSections: 8\n========================================\n\n  0:00.00  INT1\n  0:15.50  VRS1\n  0:45.20  CHO1\n  1:15.00  VRS2\n  1:45.30  CHO2\n  2:15.80  BRG1\n  2:45.00  CHO3\n  3:15.80  OUT1\n\n-> Import to REAPER: Run import_structure_markers.lua\n   and select: track_structure.json\n",[895,32918,32916],{"__ignoreMap":728},[1074,32920,32922],{"id":32921},"rest-api","REST API",[842,32924,32925],{},"For integration into web applications, MTL Audio Locators includes a FastAPI server:",[1013,32927,32930],{"className":1080,"code":32928,"filename":32929,"language":1082,"meta":728,"style":728},"poetry run serve\n","Start Server",[895,32931,32932],{"__ignoreMap":728},[1086,32933,32934,32936,32938],{"class":1088,"line":1089},[1086,32935,1115],{"class":1092},[1086,32937,11970],{"class":1096},[1086,32939,32940],{"class":1096}," serve\n",[842,32942,32943],{},"Available endpoints:",[871,32945,32946,32957],{},[874,32947,32948],{},[877,32949,32950,32953,32955],{},[880,32951,32952],{},"Endpoint",[880,32954,25716],{},[880,32956,7624],{},[887,32958,32959,32971,32983,32995,33007,33019],{},[877,32960,32961,32966,32968],{},[892,32962,32963],{},[895,32964,32965],{},"/analyze",[892,32967,4685],{},[892,32969,32970],{},"Full structure analysis",[877,32972,32973,32978,32980],{},[892,32974,32975],{},[895,32976,32977],{},"/analyze/features",[892,32979,4685],{},[892,32981,32982],{},"Audio features only (BPM, energy)",[877,32984,32985,32990,32992],{},[892,32986,32987],{},[895,32988,32989],{},"/waveform",[892,32991,4685],{},[892,32993,32994],{},"Waveform data for visualization",[877,32996,32997,33002,33004],{},[892,32998,32999],{},[895,33000,33001],{},"/engines",[892,33003,25774],{},[892,33005,33006],{},"List available backends",[877,33008,33009,33014,33016],{},[892,33010,33011],{},[895,33012,33013],{},"/logs/stream",[892,33015,25774],{},[892,33017,33018],{},"SSE stream for progress updates",[877,33020,33021,33026,33028],{},[892,33022,33023],{},[895,33024,33025],{},"/health",[892,33027,25774],{},[892,33029,33030],{},"Service health check",[842,33032,33033],{},"Example API call:",[1013,33035,33038],{"className":1080,"code":33036,"filename":33037,"language":1082,"meta":728,"style":728},"curl -X POST \"http://localhost:8000/analyze\" \\\n  -F \"file=@track.mp3\"\n","cURL",[895,33039,33040,33061],{"__ignoreMap":728},[1086,33041,33042,33045,33048,33051,33053,33056,33058],{"class":1088,"line":1089},[1086,33043,33044],{"class":1092},"curl",[1086,33046,33047],{"class":1096}," -X",[1086,33049,33050],{"class":1096}," POST",[1086,33052,1195],{"class":1146},[1086,33054,33055],{"class":1096},"http://localhost:8000/analyze",[1086,33057,1159],{"class":1146},[1086,33059,33060],{"class":1436}," \\\n",[1086,33062,33063,33066,33068,33071],{"class":1088,"line":729},[1086,33064,33065],{"class":1096},"  -F",[1086,33067,1195],{"class":1146},[1086,33069,33070],{"class":1096},"file=@track.mp3",[1086,33072,4441],{"class":1146},[842,33074,33075],{},"Response:",[1013,33077,33079],{"className":1136,"code":33078,"language":1139,"meta":728,"style":728},"{\n  \"track_id\": \"track\",\n  \"bpm\": 128,\n  \"duration_s\": 210.5,\n  \"sections\": [...],\n  \"analysis_method\": \"allin1\"\n}\n",[895,33080,33081,33085,33104,33118,33134,33151,33168],{"__ignoreMap":728},[1086,33082,33083],{"class":1088,"line":1089},[1086,33084,1147],{"class":1146},[1086,33086,33087,33089,33091,33093,33095,33097,33100,33102],{"class":1088,"line":729},[1086,33088,1152],{"class":1146},[1086,33090,32479],{"class":1155},[1086,33092,1159],{"class":1146},[1086,33094,1133],{"class":1146},[1086,33096,1195],{"class":1146},[1086,33098,33099],{"class":1096},"track",[1086,33101,1159],{"class":1146},[1086,33103,1202],{"class":1146},[1086,33105,33106,33108,33110,33112,33114,33116],{"class":1088,"line":1112},[1086,33107,1152],{"class":1146},[1086,33109,31015],{"class":1155},[1086,33111,1159],{"class":1146},[1086,33113,1133],{"class":1146},[1086,33115,9016],{"class":1187},[1086,33117,1202],{"class":1146},[1086,33119,33120,33122,33125,33127,33129,33132],{"class":1088,"line":1181},[1086,33121,1152],{"class":1146},[1086,33123,33124],{"class":1155},"duration_s",[1086,33126,1159],{"class":1146},[1086,33128,1133],{"class":1146},[1086,33130,33131],{"class":1187}," 210.5",[1086,33133,1202],{"class":1146},[1086,33135,33136,33138,33140,33142,33144,33146,33149],{"class":1088,"line":1205},[1086,33137,1152],{"class":1146},[1086,33139,30324],{"class":1155},[1086,33141,1159],{"class":1146},[1086,33143,1133],{"class":1146},[1086,33145,1217],{"class":1146},[1086,33147,33148],{"class":1436},"...",[1086,33150,9297],{"class":1146},[1086,33152,33153,33155,33158,33160,33162,33164,33166],{"class":1088,"line":1276},[1086,33154,1152],{"class":1146},[1086,33156,33157],{"class":1155},"analysis_method",[1086,33159,1159],{"class":1146},[1086,33161,1133],{"class":1146},[1086,33163,1195],{"class":1146},[1086,33165,31944],{"class":1096},[1086,33167,4441],{"class":1146},[1086,33169,33170],{"class":1088,"line":1282},[1086,33171,1291],{"class":1146},[1074,33173,33175],{"id":33174},"python-api","Python API",[1013,33177,33179],{"className":1368,"code":33178,"language":1250,"meta":728,"style":728},"from audiolocators import analyze_track, extract_audio_features\n\n# Full structure analysis\nresult = analyze_track(\"track.mp3\")\nprint(f\"BPM: {result['bpm']}\")\nfor section in result['sections']:\n    print(f\"{section['time_s']}s - {section['label']}\")\n\n# Features only (faster)\nfeatures = extract_audio_features(\"track.mp3\")\nprint(f\"Duration: {features['duration_s']}s\")\nprint(f\"Energy segments: {len(features['energy_segments'])}\")\n",[895,33180,33181,33198,33202,33207,33225,33255,33276,33325,33329,33334,33354,33387],{"__ignoreMap":728},[1086,33182,33183,33185,33188,33190,33193,33195],{"class":1088,"line":1089},[1086,33184,15570],{"class":1423},[1086,33186,33187],{"class":1436}," audiolocators ",[1086,33189,6503],{"class":1423},[1086,33191,33192],{"class":1436}," analyze_track",[1086,33194,1227],{"class":1146},[1086,33196,33197],{"class":1436}," extract_audio_features\n",[1086,33199,33200],{"class":1088,"line":729},[1086,33201,3390],{"emptyLinePlaceholder":738},[1086,33203,33204],{"class":1088,"line":1112},[1086,33205,33206],{"class":1427},"# Full structure analysis\n",[1086,33208,33209,33211,33213,33215,33217,33219,33221,33223],{"class":1088,"line":1181},[1086,33210,29358],{"class":1436},[1086,33212,1440],{"class":1146},[1086,33214,33192],{"class":1105},[1086,33216,1398],{"class":1146},[1086,33218,1159],{"class":1146},[1086,33220,30973],{"class":1096},[1086,33222,1159],{"class":1146},[1086,33224,1455],{"class":1146},[1086,33226,33227,33229,33231,33233,33235,33237,33239,33241,33243,33245,33247,33249,33251,33253],{"class":1088,"line":1205},[1086,33228,10725],{"class":1105},[1086,33230,1398],{"class":1146},[1086,33232,5962],{"class":1155},[1086,33234,32076],{"class":1096},[1086,33236,4409],{"class":1187},[1086,33238,1473],{"class":1105},[1086,33240,4340],{"class":1146},[1086,33242,10742],{"class":1146},[1086,33244,31015],{"class":1096},[1086,33246,10742],{"class":1146},[1086,33248,4420],{"class":1146},[1086,33250,4423],{"class":1187},[1086,33252,1159],{"class":1096},[1086,33254,1455],{"class":1146},[1086,33256,33257,33259,33262,33264,33266,33268,33270,33272,33274],{"class":1088,"line":1276},[1086,33258,10799],{"class":1423},[1086,33260,33261],{"class":1436}," section ",[1086,33263,5931],{"class":1423},[1086,33265,23102],{"class":1436},[1086,33267,4340],{"class":1146},[1086,33269,10742],{"class":1146},[1086,33271,30324],{"class":1096},[1086,33273,10742],{"class":1146},[1086,33275,10888],{"class":1146},[1086,33277,33278,33280,33282,33284,33286,33288,33291,33293,33295,33297,33299,33301,33303,33305,33307,33309,33311,33313,33315,33317,33319,33321,33323],{"class":1088,"line":1282},[1086,33279,11068],{"class":1105},[1086,33281,1398],{"class":1146},[1086,33283,5962],{"class":1155},[1086,33285,1159],{"class":1096},[1086,33287,4409],{"class":1187},[1086,33289,33290],{"class":1105},"section",[1086,33292,4340],{"class":1146},[1086,33294,10742],{"class":1146},[1086,33296,32543],{"class":1096},[1086,33298,10742],{"class":1146},[1086,33300,4420],{"class":1146},[1086,33302,4423],{"class":1187},[1086,33304,29507],{"class":1096},[1086,33306,4409],{"class":1187},[1086,33308,33290],{"class":1105},[1086,33310,4340],{"class":1146},[1086,33312,10742],{"class":1146},[1086,33314,10840],{"class":1096},[1086,33316,10742],{"class":1146},[1086,33318,4420],{"class":1146},[1086,33320,4423],{"class":1187},[1086,33322,1159],{"class":1096},[1086,33324,1455],{"class":1146},[1086,33326,33327],{"class":1088,"line":1288},[1086,33328,3390],{"emptyLinePlaceholder":738},[1086,33330,33331],{"class":1088,"line":2685},[1086,33332,33333],{"class":1427},"# Features only (faster)\n",[1086,33335,33336,33339,33341,33344,33346,33348,33350,33352],{"class":1088,"line":2700},[1086,33337,33338],{"class":1436},"features ",[1086,33340,1440],{"class":1146},[1086,33342,33343],{"class":1105}," extract_audio_features",[1086,33345,1398],{"class":1146},[1086,33347,1159],{"class":1146},[1086,33349,30973],{"class":1096},[1086,33351,1159],{"class":1146},[1086,33353,1455],{"class":1146},[1086,33355,33356,33358,33360,33362,33365,33367,33370,33372,33374,33376,33378,33380,33382,33385],{"class":1088,"line":3398},[1086,33357,10725],{"class":1105},[1086,33359,1398],{"class":1146},[1086,33361,5962],{"class":1155},[1086,33363,33364],{"class":1096},"\"Duration: ",[1086,33366,4409],{"class":1187},[1086,33368,33369],{"class":1105},"features",[1086,33371,4340],{"class":1146},[1086,33373,10742],{"class":1146},[1086,33375,33124],{"class":1096},[1086,33377,10742],{"class":1146},[1086,33379,4420],{"class":1146},[1086,33381,4423],{"class":1187},[1086,33383,33384],{"class":1096},"s\"",[1086,33386,1455],{"class":1146},[1086,33388,33389,33391,33393,33395,33398,33400,33403,33405,33407,33409,33411,33414,33416,33418,33420,33422],{"class":1088,"line":1715},[1086,33390,10725],{"class":1105},[1086,33392,1398],{"class":1146},[1086,33394,5962],{"class":1155},[1086,33396,33397],{"class":1096},"\"Energy segments: ",[1086,33399,4409],{"class":1187},[1086,33401,33402],{"class":1105},"len",[1086,33404,1398],{"class":1146},[1086,33406,33369],{"class":1105},[1086,33408,4340],{"class":1146},[1086,33410,10742],{"class":1146},[1086,33412,33413],{"class":1096},"energy_segments",[1086,33415,10742],{"class":1146},[1086,33417,20587],{"class":1146},[1086,33419,4423],{"class":1187},[1086,33421,1159],{"class":1096},[1086,33423,1455],{"class":1146},[863,33425,33427],{"id":33426},"real-time-progress-streaming","Real-Time Progress Streaming",[842,33429,33430],{},"For long-running analyses, the API provides Server-Sent Events (SSE) for real-time progress updates:",[1013,33432,33436],{"className":33433,"code":33434,"language":33435,"meta":728,"style":728},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const eventSource = new EventSource('http://localhost:8000/logs/stream');\n\neventSource.onmessage = (event) => {\n  const log = JSON.parse(event.data);\n  console.log(`[${log.source}] ${log.message}`);\n  // Example: [allin1] Separating stems (vocals, drums, bass, other)...\n};\n","javascript",[895,33437,33438,33467,33471,33493,33521,33564,33569],{"__ignoreMap":728},[1086,33439,33440,33443,33446,33448,33450,33453,33455,33457,33460,33462,33464],{"class":1088,"line":1089},[1086,33441,33442],{"class":1155},"const",[1086,33444,33445],{"class":1436}," eventSource ",[1086,33447,1440],{"class":1146},[1086,33449,26591],{"class":1146},[1086,33451,33452],{"class":1105}," EventSource",[1086,33454,1398],{"class":1436},[1086,33456,10742],{"class":1146},[1086,33458,33459],{"class":1096},"http://localhost:8000/logs/stream",[1086,33461,10742],{"class":1146},[1086,33463,1410],{"class":1436},[1086,33465,33466],{"class":1146},";\n",[1086,33468,33469],{"class":1088,"line":729},[1086,33470,3390],{"emptyLinePlaceholder":738},[1086,33472,33473,33476,33478,33481,33483,33485,33487,33489,33491],{"class":1088,"line":1112},[1086,33474,33475],{"class":1436},"eventSource",[1086,33477,861],{"class":1146},[1086,33479,33480],{"class":1105},"onmessage",[1086,33482,19552],{"class":1146},[1086,33484,5979],{"class":1146},[1086,33486,21543],{"class":1401},[1086,33488,1410],{"class":1146},[1086,33490,26610],{"class":1155},[1086,33492,1164],{"class":1146},[1086,33494,33495,33497,33500,33502,33504,33506,33509,33511,33513,33515,33517,33519],{"class":1088,"line":1181},[1086,33496,26450],{"class":1155},[1086,33498,33499],{"class":1436}," log",[1086,33501,19552],{"class":1146},[1086,33503,26986],{"class":1436},[1086,33505,861],{"class":1146},[1086,33507,33508],{"class":1105},"parse",[1086,33510,1398],{"class":4109},[1086,33512,21543],{"class":1436},[1086,33514,861],{"class":1146},[1086,33516,4337],{"class":1436},[1086,33518,1410],{"class":4109},[1086,33520,33466],{"class":1146},[1086,33522,33523,33526,33528,33531,33533,33535,33537,33539,33541,33543,33545,33547,33550,33552,33554,33556,33558,33560,33562],{"class":1088,"line":1205},[1086,33524,33525],{"class":1436},"  console",[1086,33527,861],{"class":1146},[1086,33529,33530],{"class":1105},"log",[1086,33532,1398],{"class":4109},[1086,33534,27209],{"class":1146},[1086,33536,4340],{"class":1096},[1086,33538,26521],{"class":1146},[1086,33540,33530],{"class":1436},[1086,33542,861],{"class":1146},[1086,33544,27530],{"class":1436},[1086,33546,4423],{"class":1146},[1086,33548,33549],{"class":1096},"] ",[1086,33551,26521],{"class":1146},[1086,33553,33530],{"class":1436},[1086,33555,861],{"class":1146},[1086,33557,15922],{"class":1436},[1086,33559,26527],{"class":1146},[1086,33561,1410],{"class":4109},[1086,33563,33466],{"class":1146},[1086,33565,33566],{"class":1088,"line":1276},[1086,33567,33568],{"class":1427},"  // Example: [allin1] Separating stems (vocals, drums, bass, other)...\n",[1086,33570,33571],{"class":1088,"line":1282},[1086,33572,33573],{"class":1146},"};\n",[842,33575,33576],{},"Progress stages for allin1 analysis:",[991,33578,33579,33585,33591,33597,33603],{},[961,33580,33581,33584],{},[996,33582,33583],{},"Starting"," – Pipeline initialization",[961,33586,33587,33590],{},[996,33588,33589],{},"Demixing"," – Stem separation (0-100%)",[961,33592,33593,33596],{},[996,33594,33595],{},"Spectrograms"," – Feature extraction",[961,33598,33599,33602],{},[996,33600,33601],{},"Analyzing"," – Neural network inference",[961,33604,33605,33608],{},[996,33606,33607],{},"Complete"," – Results ready",[863,33610,30880],{"id":30879},[1074,33612,33614],{"id":33613},"integration-with-wavesurferjs","Integration with WaveSurfer.js",[842,33616,33617],{},"Combine structure data with waveform visualization:",[1013,33619,33621],{"className":33433,"code":33620,"language":33435,"meta":728,"style":728},"import WaveSurfer from 'wavesurfer.js';\nimport RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.js';\n\nconst wavesurfer = WaveSurfer.create({\n  container: '#waveform',\n  plugins: [RegionsPlugin.create()]\n});\n\n// After analysis\nstructure.sections.forEach((section, i) => {\n  const nextSection = structure.sections[i + 1];\n  wavesurfer.addRegion({\n    start: section.time_s,\n    end: nextSection ? nextSection.time_s : wavesurfer.getDuration(),\n    color: section.color + '40', // Add transparency\n    content: section.label\n  });\n});\n",[895,33622,33623,33641,33659,33663,33683,33699,33716,33724,33728,33733,33764,33794,33808,33824,33857,33884,33898,33906],{"__ignoreMap":728},[1086,33624,33625,33627,33630,33632,33634,33637,33639],{"class":1088,"line":1089},[1086,33626,6503],{"class":1423},[1086,33628,33629],{"class":1436}," WaveSurfer ",[1086,33631,15570],{"class":1423},[1086,33633,26405],{"class":1146},[1086,33635,33636],{"class":1096},"wavesurfer.js",[1086,33638,10742],{"class":1146},[1086,33640,33466],{"class":1146},[1086,33642,33643,33645,33648,33650,33652,33655,33657],{"class":1088,"line":729},[1086,33644,6503],{"class":1423},[1086,33646,33647],{"class":1436}," RegionsPlugin ",[1086,33649,15570],{"class":1423},[1086,33651,26405],{"class":1146},[1086,33653,33654],{"class":1096},"wavesurfer.js/dist/plugins/regions.js",[1086,33656,10742],{"class":1146},[1086,33658,33466],{"class":1146},[1086,33660,33661],{"class":1088,"line":1112},[1086,33662,3390],{"emptyLinePlaceholder":738},[1086,33664,33665,33667,33670,33672,33675,33677,33679,33681],{"class":1088,"line":1181},[1086,33666,33442],{"class":1155},[1086,33668,33669],{"class":1436}," wavesurfer ",[1086,33671,1440],{"class":1146},[1086,33673,33674],{"class":1436}," WaveSurfer",[1086,33676,861],{"class":1146},[1086,33678,4543],{"class":1105},[1086,33680,1398],{"class":1436},[1086,33682,1147],{"class":1146},[1086,33684,33685,33688,33690,33692,33695,33697],{"class":1088,"line":1205},[1086,33686,33687],{"class":4109},"  container",[1086,33689,1133],{"class":1146},[1086,33691,26405],{"class":1146},[1086,33693,33694],{"class":1096},"#waveform",[1086,33696,10742],{"class":1146},[1086,33698,1202],{"class":1146},[1086,33700,33701,33704,33706,33709,33711,33713],{"class":1088,"line":1276},[1086,33702,33703],{"class":4109},"  plugins",[1086,33705,1133],{"class":1146},[1086,33707,33708],{"class":1436}," [RegionsPlugin",[1086,33710,861],{"class":1146},[1086,33712,4543],{"class":1105},[1086,33714,33715],{"class":1436},"()]\n",[1086,33717,33718,33720,33722],{"class":1088,"line":1282},[1086,33719,4423],{"class":1146},[1086,33721,1410],{"class":1436},[1086,33723,33466],{"class":1146},[1086,33725,33726],{"class":1088,"line":1288},[1086,33727,3390],{"emptyLinePlaceholder":738},[1086,33729,33730],{"class":1088,"line":2685},[1086,33731,33732],{"class":1427},"// After analysis\n",[1086,33734,33735,33738,33740,33742,33744,33747,33749,33751,33753,33755,33758,33760,33762],{"class":1088,"line":2700},[1086,33736,33737],{"class":1436},"structure",[1086,33739,861],{"class":1146},[1086,33741,30324],{"class":1436},[1086,33743,861],{"class":1146},[1086,33745,33746],{"class":1105},"forEach",[1086,33748,1398],{"class":1436},[1086,33750,1398],{"class":1146},[1086,33752,33290],{"class":1401},[1086,33754,1227],{"class":1146},[1086,33756,33757],{"class":1401}," i",[1086,33759,1410],{"class":1146},[1086,33761,26610],{"class":1155},[1086,33763,1164],{"class":1146},[1086,33765,33766,33768,33771,33773,33776,33778,33780,33782,33785,33788,33790,33792],{"class":1088,"line":3398},[1086,33767,26450],{"class":1155},[1086,33769,33770],{"class":1436}," nextSection",[1086,33772,19552],{"class":1146},[1086,33774,33775],{"class":1436}," structure",[1086,33777,861],{"class":1146},[1086,33779,30324],{"class":1436},[1086,33781,4340],{"class":4109},[1086,33783,33784],{"class":1436},"i",[1086,33786,33787],{"class":1146}," +",[1086,33789,23488],{"class":1187},[1086,33791,4420],{"class":4109},[1086,33793,33466],{"class":1146},[1086,33795,33796,33799,33801,33804,33806],{"class":1088,"line":1715},[1086,33797,33798],{"class":1436},"  wavesurfer",[1086,33800,861],{"class":1146},[1086,33802,33803],{"class":1105},"addRegion",[1086,33805,1398],{"class":4109},[1086,33807,1147],{"class":1146},[1086,33809,33810,33813,33815,33818,33820,33822],{"class":1088,"line":3409},[1086,33811,33812],{"class":4109},"    start",[1086,33814,1133],{"class":1146},[1086,33816,33817],{"class":1436}," section",[1086,33819,861],{"class":1146},[1086,33821,32543],{"class":1436},[1086,33823,1202],{"class":1146},[1086,33825,33826,33829,33831,33833,33836,33838,33840,33842,33845,33848,33850,33853,33855],{"class":1088,"line":3415},[1086,33827,33828],{"class":4109},"    end",[1086,33830,1133],{"class":1146},[1086,33832,33770],{"class":1436},[1086,33834,33835],{"class":1146}," ?",[1086,33837,33770],{"class":1436},[1086,33839,861],{"class":1146},[1086,33841,32543],{"class":1436},[1086,33843,33844],{"class":1146}," :",[1086,33846,33847],{"class":1436}," wavesurfer",[1086,33849,861],{"class":1146},[1086,33851,33852],{"class":1105},"getDuration",[1086,33854,2516],{"class":4109},[1086,33856,1202],{"class":1146},[1086,33858,33859,33862,33864,33866,33868,33870,33872,33874,33877,33879,33881],{"class":1088,"line":3421},[1086,33860,33861],{"class":4109},"    color",[1086,33863,1133],{"class":1146},[1086,33865,33817],{"class":1436},[1086,33867,861],{"class":1146},[1086,33869,31252],{"class":1436},[1086,33871,33787],{"class":1146},[1086,33873,26405],{"class":1146},[1086,33875,33876],{"class":1096},"40",[1086,33878,10742],{"class":1146},[1086,33880,1227],{"class":1146},[1086,33882,33883],{"class":1427}," // Add transparency\n",[1086,33885,33886,33889,33891,33893,33895],{"class":1088,"line":3427},[1086,33887,33888],{"class":4109},"    content",[1086,33890,1133],{"class":1146},[1086,33892,33817],{"class":1436},[1086,33894,861],{"class":1146},[1086,33896,33897],{"class":1436},"label\n",[1086,33899,33900,33902,33904],{"class":1088,"line":3433},[1086,33901,4797],{"class":1146},[1086,33903,1410],{"class":4109},[1086,33905,33466],{"class":1146},[1086,33907,33908,33910,33912],{"class":1088,"line":3439},[1086,33909,4423],{"class":1146},[1086,33911,1410],{"class":1436},[1086,33913,33466],{"class":1146},[1074,33915,33917],{"id":33916},"daw-import-reaper","DAW Import (REAPER)",[842,33919,33920],{},"The JSON format is designed for easy DAW import. A companion Lua script for REAPER can read the output and create project markers:",[1013,33922,33926],{"className":33923,"code":33924,"language":33925,"meta":728,"style":728},"language-lua shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- In REAPER: Actions > Run ReaScript\n-- Select: import_structure_markers.lua\n","lua",[895,33927,33928,33933],{"__ignoreMap":728},[1086,33929,33930],{"class":1088,"line":1089},[1086,33931,33932],{},"-- In REAPER: Actions > Run ReaScript\n",[1086,33934,33935],{"class":1088,"line":729},[1086,33936,33937],{},"-- Select: import_structure_markers.lua\n",[1074,33939,33941],{"id":33940},"combined-with-mtl-ableton-analyser","Combined with MTL Ableton Analyser",[842,33943,33944],{},"Use both tools together for comprehensive music analysis:",[991,33946,33947,33952,33957],{},[961,33948,33949,33951],{},[996,33950,31832],{}," – Analyze raw audio files",[961,33953,33954,33956],{},[996,33955,30096],{}," – Extract data from Ableton projects",[961,33958,33959,33962],{},[996,33960,33961],{},"Compare"," – Validate AI detection against manual markers",[863,33964,33966],{"id":33965},"accuracy-considerations","Accuracy Considerations",[842,33968,33969],{},"Structure detection accuracy depends on several factors:",[871,33971,33972,33985],{},[874,33973,33974],{},[877,33975,33976,33979,33982],{},[880,33977,33978],{},"Factor",[880,33980,33981],{},"Impact",[880,33983,33984],{},"Mitigation",[887,33986,33987,33999,34012,34025],{},[877,33988,33989,33993,33996],{},[892,33990,33991],{},[996,33992,8901],{},[892,33994,33995],{},"Pop/EDM highest accuracy",[892,33997,33998],{},"Use genre-appropriate backend",[877,34000,34001,34006,34009],{},[892,34002,34003],{},[996,34004,34005],{},"Mixing",[892,34007,34008],{},"Clear separations help",[892,34010,34011],{},"allin1 handles complex mixes well",[877,34013,34014,34019,34022],{},[892,34015,34016],{},[996,34017,34018],{},"Length",[892,34020,34021],{},"Very long tracks may segment oddly",[892,34023,34024],{},"Consider splitting before analysis",[877,34026,34027,34032,34035],{},[892,34028,34029],{},[996,34030,34031],{},"Tempo changes",[892,34033,34034],{},"Can confuse boundary detection",[892,34036,34037],{},"Manual review recommended",[842,34039,34040],{},"For production use, we recommend:",[958,34042,34043,34048,34053],{},[961,34044,34045,34047],{},[996,34046,31944],{}," for final output (highest accuracy)",[961,34049,34050,34052],{},[996,34051,31979],{}," for quick previews or batch processing",[961,34054,34055,34058],{},[996,34056,34057],{},"Manual review"," for critical applications",[863,34060,34062],{"id":34061},"platform-specific-installation-notes","Platform-Specific Installation Notes",[1074,34064,34066],{"id":34065},"the-allin1-dependency-challenge","The allin1 Dependency Challenge",[842,34068,34069],{},"The allin1 engine provides the highest quality analysis, but it has a complex dependency chain that currently causes compatibility issues across platforms:",[1013,34071,34074],{"className":34072,"code":34073,"language":1018},[1016],"allin1 1.1.0\n├── natten (Neighborhood Attention)\n│   └── API changed in 0.21.x, allin1 expects older API\n├── madmom (Music Beat Detection)\n│   └── Uses collections.MutableSequence, removed in Python 3.10+\n└── demucs (stem separation)\n    └── Works fine\n",[895,34075,34073],{"__ignoreMap":728},[871,34077,34078,34090],{},[874,34079,34080],{},[877,34081,34082,34085,34088],{},[880,34083,34084],{},"Dependency",[880,34086,34087],{},"Problem",[880,34089,33981],{},[887,34091,34092,34106,34120],{},[877,34093,34094,34100,34103],{},[892,34095,34096,34099],{},[895,34097,34098],{},"natten"," 0.21.x",[892,34101,34102],{},"New API incompatible with allin1",[892,34104,34105],{},"Import fails on all platforms",[877,34107,34108,34114,34117],{},[892,34109,34110,34113],{},[895,34111,34112],{},"madmom"," 0.16.1",[892,34115,34116],{},"Uses deprecated Python API",[892,34118,34119],{},"Breaks on Python 3.10+",[877,34121,34122,34125,34128],{},[892,34123,34124],{},"shi-labs.com wheels",[892,34126,34127],{},"SSL certificate expired",[892,34129,34130],{},"Can't download pre-built binaries",[842,34132,34133,34136,34137,34140],{},[996,34134,34135],{},"Current recommendation:"," Use ",[996,34138,34139],{},"librosa fallback"," which works reliably across all platforms. The allin1 ecosystem requires updates from upstream maintainers.",[1013,34142,34144],{"className":1080,"code":34143,"language":1082,"meta":728,"style":728},"# What you'll see when running analysis:\n$ poetry run analyze track.mp3\nUsing librosa enhanced analysis  # allin1 not available, fallback works fine\n",[895,34145,34146,34151,34164],{"__ignoreMap":728},[1086,34147,34148],{"class":1088,"line":1089},[1086,34149,34150],{"class":1427},"# What you'll see when running analysis:\n",[1086,34152,34153,34156,34158,34160,34162],{"class":1088,"line":729},[1086,34154,34155],{"class":1092},"$",[1086,34157,11961],{"class":1096},[1086,34159,11970],{"class":1096},[1086,34161,31441],{"class":1096},[1086,34163,32887],{"class":1096},[1086,34165,34166,34169,34172,34175,34178],{"class":1088,"line":1112},[1086,34167,34168],{"class":1092},"Using",[1086,34170,34171],{"class":1096}," librosa",[1086,34173,34174],{"class":1096}," enhanced",[1086,34176,34177],{"class":1096}," analysis",[1086,34179,34180],{"class":1427},"  # allin1 not available, fallback works fine\n",[1074,34182,34184],{"id":34183},"running-with-docker","Running with Docker",[842,34186,34187],{},"We provide a Dockerfile for containerized deployment:",[1013,34189,34194],{"className":34190,"code":34191,"filename":34192,"language":34193,"meta":728,"style":728},"language-dockerfile shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","FROM python:3.10-slim\n\nWORKDIR /app\n\n# System dependencies\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    ffmpeg libsndfile1 git cmake build-essential \\\n    && rm -rf /var/lib/apt/lists/*\n\nRUN pip install --upgrade pip Cython numpy poetry\n\nCOPY pyproject.toml poetry.lock README.md ./\nRUN poetry config virtualenvs.create false \\\n    && poetry install --no-interaction --no-ansi --no-root\n\nCOPY src/ ./src/\nRUN poetry install --no-interaction --no-ansi --only-root\n\nEXPOSE 8000\nCMD [\"python\", \"-m\", \"uvicorn\", \"audiolocators.server:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]\n","Dockerfile","dockerfile",[895,34195,34196,34203,34207,34215,34219,34224,34232,34237,34242,34246,34253,34257,34265,34272,34277,34281,34288,34295,34299,34307],{"__ignoreMap":728},[1086,34197,34198,34200],{"class":1088,"line":1089},[1086,34199,2470],{"class":1187},[1086,34201,34202],{"class":1436}," python:3.10-slim\n",[1086,34204,34205],{"class":1088,"line":729},[1086,34206,3390],{"emptyLinePlaceholder":738},[1086,34208,34209,34212],{"class":1088,"line":1112},[1086,34210,34211],{"class":1187},"WORKDIR",[1086,34213,34214],{"class":1436}," /app\n",[1086,34216,34217],{"class":1088,"line":1181},[1086,34218,3390],{"emptyLinePlaceholder":738},[1086,34220,34221],{"class":1088,"line":1205},[1086,34222,34223],{"class":1427},"# System dependencies\n",[1086,34225,34226,34229],{"class":1088,"line":1276},[1086,34227,34228],{"class":1187},"RUN",[1086,34230,34231],{"class":1436}," apt-get update && apt-get install -y --no-install-recommends \\\n",[1086,34233,34234],{"class":1088,"line":1282},[1086,34235,34236],{"class":1436},"    ffmpeg libsndfile1 git cmake build-essential \\\n",[1086,34238,34239],{"class":1088,"line":1288},[1086,34240,34241],{"class":1436},"    && rm -rf /var/lib/apt/lists/*\n",[1086,34243,34244],{"class":1088,"line":2685},[1086,34245,3390],{"emptyLinePlaceholder":738},[1086,34247,34248,34250],{"class":1088,"line":2700},[1086,34249,34228],{"class":1187},[1086,34251,34252],{"class":1436}," pip install --upgrade pip Cython numpy poetry\n",[1086,34254,34255],{"class":1088,"line":3398},[1086,34256,3390],{"emptyLinePlaceholder":738},[1086,34258,34259,34262],{"class":1088,"line":1715},[1086,34260,34261],{"class":1187},"COPY",[1086,34263,34264],{"class":1436}," pyproject.toml poetry.lock README.md ./\n",[1086,34266,34267,34269],{"class":1088,"line":3409},[1086,34268,34228],{"class":1187},[1086,34270,34271],{"class":1436}," poetry config virtualenvs.create false \\\n",[1086,34273,34274],{"class":1088,"line":3415},[1086,34275,34276],{"class":1436},"    && poetry install --no-interaction --no-ansi --no-root\n",[1086,34278,34279],{"class":1088,"line":3421},[1086,34280,3390],{"emptyLinePlaceholder":738},[1086,34282,34283,34285],{"class":1088,"line":3427},[1086,34284,34261],{"class":1187},[1086,34286,34287],{"class":1436}," src/ ./src/\n",[1086,34289,34290,34292],{"class":1088,"line":3433},[1086,34291,34228],{"class":1187},[1086,34293,34294],{"class":1436}," poetry install --no-interaction --no-ansi --only-root\n",[1086,34296,34297],{"class":1088,"line":3439},[1086,34298,3390],{"emptyLinePlaceholder":738},[1086,34300,34301,34304],{"class":1088,"line":3444},[1086,34302,34303],{"class":1187},"EXPOSE",[1086,34305,34306],{"class":1436}," 8000\n",[1086,34308,34309,34312,34314,34317,34319,34322,34324,34327,34329,34332,34334,34337,34339,34342,34344,34347,34349,34352],{"class":1088,"line":3450},[1086,34310,34311],{"class":1187},"CMD",[1086,34313,1217],{"class":1436},[1086,34315,34316],{"class":1096},"\"python\"",[1086,34318,5660],{"class":1436},[1086,34320,34321],{"class":1096},"\"-m\"",[1086,34323,5660],{"class":1436},[1086,34325,34326],{"class":1096},"\"uvicorn\"",[1086,34328,5660],{"class":1436},[1086,34330,34331],{"class":1096},"\"audiolocators.server:app\"",[1086,34333,5660],{"class":1436},[1086,34335,34336],{"class":1096},"\"--host\"",[1086,34338,5660],{"class":1436},[1086,34340,34341],{"class":1096},"\"0.0.0.0\"",[1086,34343,5660],{"class":1436},[1086,34345,34346],{"class":1096},"\"--port\"",[1086,34348,5660],{"class":1436},[1086,34350,34351],{"class":1096},"\"8000\"",[1086,34353,1273],{"class":1436},[1013,34355,34358],{"className":1080,"code":34356,"filename":34357,"language":1082,"meta":728,"style":728},"# Build the image\ndocker build -t mtl-audiolocators .\n\n# Run analysis (uses librosa)\ndocker run -v $(pwd)/tracks:/app/tracks mtl-audiolocators \\\n    python -c \"from audiolocators import analyze_track; print(analyze_track('/app/tracks/song.mp3'))\"\n\n# Start the API server\ndocker run -p 8000:8000 mtl-audiolocators\ncurl http://localhost:8000/health\n# {\"status\":\"ok\",\"allin1_available\":false}\n","Build and Run",[895,34359,34360,34365,34382,34386,34391,34415,34429,34433,34438,34451,34458],{"__ignoreMap":728},[1086,34361,34362],{"class":1088,"line":1089},[1086,34363,34364],{"class":1427},"# Build the image\n",[1086,34366,34367,34370,34373,34376,34379],{"class":1088,"line":729},[1086,34368,34369],{"class":1092},"docker",[1086,34371,34372],{"class":1096}," build",[1086,34374,34375],{"class":1096}," -t",[1086,34377,34378],{"class":1096}," mtl-audiolocators",[1086,34380,34381],{"class":1096}," .\n",[1086,34383,34384],{"class":1088,"line":1112},[1086,34385,3390],{"emptyLinePlaceholder":738},[1086,34387,34388],{"class":1088,"line":1181},[1086,34389,34390],{"class":1427},"# Run analysis (uses librosa)\n",[1086,34392,34393,34395,34397,34400,34403,34406,34408,34411,34413],{"class":1088,"line":1205},[1086,34394,34369],{"class":1092},[1086,34396,11970],{"class":1096},[1086,34398,34399],{"class":1096}," -v",[1086,34401,34402],{"class":1146}," $(",[1086,34404,34405],{"class":1105},"pwd",[1086,34407,1410],{"class":1146},[1086,34409,34410],{"class":1096},"/tracks:/app/tracks",[1086,34412,34378],{"class":1096},[1086,34414,33060],{"class":1436},[1086,34416,34417,34420,34422,34424,34427],{"class":1088,"line":1276},[1086,34418,34419],{"class":1096},"    python",[1086,34421,8574],{"class":1096},[1086,34423,1195],{"class":1146},[1086,34425,34426],{"class":1096},"from audiolocators import analyze_track; print(analyze_track('/app/tracks/song.mp3'))",[1086,34428,4441],{"class":1146},[1086,34430,34431],{"class":1088,"line":1282},[1086,34432,3390],{"emptyLinePlaceholder":738},[1086,34434,34435],{"class":1088,"line":1288},[1086,34436,34437],{"class":1427},"# Start the API server\n",[1086,34439,34440,34442,34444,34446,34449],{"class":1088,"line":2685},[1086,34441,34369],{"class":1092},[1086,34443,11970],{"class":1096},[1086,34445,15405],{"class":1096},[1086,34447,34448],{"class":1096}," 8000:8000",[1086,34450,32860],{"class":1096},[1086,34452,34453,34455],{"class":1088,"line":2700},[1086,34454,33044],{"class":1092},[1086,34456,34457],{"class":1096}," http://localhost:8000/health\n",[1086,34459,34460],{"class":1088,"line":3398},[1086,34461,34462],{"class":1427},"# {\"status\":\"ok\",\"allin1_available\":false}\n",[1074,34464,34466],{"id":34465},"version-compatibility-matrix","Version Compatibility Matrix",[871,34468,34469,34484],{},[874,34470,34471],{},[877,34472,34473,34476,34478,34480,34482],{},[880,34474,34475],{},"Python",[880,34477,34112],{},[880,34479,34098],{},[880,34481,31944],{},[880,34483,22373],{},[887,34485,34486,34503,34518,34534],{},[877,34487,34488,34491,34494,34497,34500],{},[892,34489,34490],{},"3.9",[892,34492,34493],{},"0.16.1",[892,34495,34496],{},"0.17.x",[892,34498,34499],{},"1.1.0",[892,34501,34502],{},"Best chance for allin1",[877,34504,34505,34508,34510,34513,34515],{},[892,34506,34507],{},"3.10+",[892,34509,34493],{},[892,34511,34512],{},"any",[892,34514,34499],{},[892,34516,34517],{},"madmom fails (deprecated API)",[877,34519,34520,34523,34526,34529,34531],{},[892,34521,34522],{},"3.11",[892,34524,34525],{},"–",[892,34527,34528],{},"0.21.x",[892,34530,34499],{},[892,34532,34533],{},"natten API mismatch",[877,34535,34536,34540,34542,34544,34546],{},[892,34537,34538],{},[996,34539,34512],{},[892,34541,34525],{},[892,34543,34525],{},[892,34545,34525],{},[892,34547,34548],{},[996,34549,34550],{},"librosa fallback always works",[1572,34552,34553],{},[842,34554,34555,34558,34559,861],{},[996,34556,34557],{},"Contributing:"," If you get allin1 working reliably, please share your setup! Open a PR or issue on ",[846,34560,15320],{"href":34561,"rel":34562},"https://github.com/musictechlab/mtl-audiolocators",[850],[863,34564,31659],{"id":31658},[842,34566,34567],{},"Current limitations to be aware of:",[958,34569,34570,34576,34586,34592,34598],{},[961,34571,34572,34575],{},[996,34573,34574],{},"allin1 availability"," – Due to dependency conflicts (natten API changes, madmom Python 3.10+ incompatibility), allin1 currently doesn't work on most setups. Use librosa fallback instead.",[961,34577,34578,34581,34582,34585],{},[996,34579,34580],{},"Label accuracy"," – Section ",[964,34583,34584],{},"types"," (verse vs. chorus) are heuristic-based, boundaries are more reliable than labels",[961,34587,34588,34591],{},[996,34589,34590],{},"Genre dependency"," – Works best on pop/EDM with clear structure; experimental/ambient music may produce inconsistent results",[961,34593,34594,34597],{},[996,34595,34596],{},"Processing time"," – If allin1 were working, it would take 2-5x real-time on CPU",[961,34599,34600,34603],{},[996,34601,34602],{},"Model downloads"," – allin1 requires ~1.5GB model download on first run",[4937,34605],{},[863,34607,34609],{"id":34608},"alternative-mir-tools","Alternative MIR Tools",[842,34611,34612],{},"If you're exploring music structure analysis beyond MTL Audio Locators, here are other tools worth considering:",[1074,34614,34616],{"id":34615},"open-source-libraries-for-structure-analysis","Open Source Libraries for Structure Analysis",[871,34618,34619,34633],{},[874,34620,34621],{},[877,34622,34623,34625,34628,34631],{},[880,34624,882],{},[880,34626,34627],{},"Focus",[880,34629,34630],{},"Structure Detection",[880,34632,2719],{},[887,34634,34635,34653,34671,34691],{},[877,34636,34637,34644,34647,34650],{},[892,34638,34639],{},[996,34640,34641],{},[846,34642,31944],{"href":31999,"rel":34643},[850],[892,34645,34646],{},"Full structure",[892,34648,34649],{},"Yes (verse, chorus, bridge)",[892,34651,34652],{},"Best quality, but dependency issues",[877,34654,34655,34662,34665,34668],{},[892,34656,34657],{},[996,34658,34659],{},[846,34660,31962],{"href":32163,"rel":34661},[850],[892,34663,34664],{},"Segmentation",[892,34666,34667],{},"Abstract labels (A, B, C)",[892,34669,34670],{},"Academic, multiple algorithms",[877,34672,34673,34682,34685,34688],{},[892,34674,34675],{},[996,34676,34677],{},[846,34678,34681],{"href":34679,"rel":34680},"https://essentia.upf.edu",[850],"Essentia",[892,34683,34684],{},"Comprehensive MIR",[892,34686,34687],{},"Basic segmentation",[892,34689,34690],{},"MTG Barcelona, well maintained",[877,34692,34693,34701,34704,34707],{},[892,34694,34695],{},[996,34696,34697],{},[846,34698,31979],{"href":34699,"rel":34700},"https://librosa.org",[850],[892,34702,34703],{},"Audio analysis",[892,34705,34706],{},"Heuristic-based",[892,34708,34709],{},"Always works, lower accuracy",[1074,34711,34713],{"id":34712},"beattempo-detection-no-structure-labels","Beat/Tempo Detection (No Structure Labels)",[871,34715,34716,34726],{},[874,34717,34718],{},[877,34719,34720,34722,34724],{},[880,34721,882],{},[880,34723,34627],{},[880,34725,2719],{},[887,34727,34728,34744,34761],{},[877,34729,34730,34738,34741],{},[892,34731,34732],{},[996,34733,34734],{},[846,34735,34112],{"href":34736,"rel":34737},"https://github.com/CPJKU/madmom",[850],[892,34739,34740],{},"Beat/onset detection",[892,34742,34743],{},"Strong beat tracking, Python 3.9 only",[877,34745,34746,34755,34758],{},[892,34747,34748],{},[996,34749,34750],{},[846,34751,34754],{"href":34752,"rel":34753},"https://aubio.org",[850],"Aubio",[892,34756,34757],{},"Lightweight MIR",[892,34759,34760],{},"Fast, C library with Python bindings",[877,34762,34763,34772,34775],{},[892,34764,34765],{},[996,34766,34767],{},[846,34768,34771],{"href":34769,"rel":34770},"https://github.com/mjhydri/BeatNet",[850],"BeatNet",[892,34773,34774],{},"Beat/downbeat",[892,34776,34777],{},"Real-time, deep learning",[1074,34779,34781],{"id":34780},"commercial-apis-no-structure-detection","Commercial APIs (No Structure Detection)",[1901,34783,34784],{},[842,34785,34786,34788],{},[996,34787,30853],{}," No major commercial API currently offers song structure detection (verse/chorus/bridge). The services below provide other audio analysis features:",[871,34790,34791,34804],{},[874,34792,34793],{},[877,34794,34795,34798,34801],{},[880,34796,34797],{},"Service",[880,34799,34800],{},"Features",[880,34802,34803],{},"What It Does NOT Do",[887,34805,34806,34823,34841],{},[877,34807,34808,34817,34820],{},[892,34809,34810],{},[996,34811,34812],{},[846,34813,34816],{"href":34814,"rel":34815},"https://cyanite.ai",[850],"Cyanite.ai",[892,34818,34819],{},"Mood, genre, instruments (15s segments)",[892,34821,34822],{},"No verse/chorus labels",[877,34824,34825,34835,34838],{},[892,34826,34827,34834],{},[996,34828,34829],{},[846,34830,34833],{"href":34831,"rel":34832},"https://www.beatport.com",[850],"Musiio"," (Beatport)",[892,34836,34837],{},"Genre, mood, BPM",[892,34839,34840],{},"No structure detection",[877,34842,34843,34852,34855],{},[892,34844,34845],{},[996,34846,34847],{},[846,34848,34851],{"href":34849,"rel":34850},"https://www.acrcloud.com",[850],"ACRCloud",[892,34853,34854],{},"Audio fingerprinting, recognition",[892,34856,34840],{},[1572,34858,34859],{},[842,34860,34861,34864,34865,34868],{},[996,34862,34863],{},"Spotify Audio Analysis API"," was the only major commercial option for structure detection (sections, segments, tempo, key). It was ",[996,34866,34867],{},"deprecated in November 2024"," and is no longer available.",[1074,34870,34872],{"id":34871},"why-no-commercial-structure-apis","Why No Commercial Structure APIs?",[842,34874,34875],{},"Song structure detection is a niche problem with limited commercial demand:",[958,34877,34878,34884,34890],{},[961,34879,34880,34883],{},[996,34881,34882],{},"Music streaming"," – services use internal solutions (not exposed via API)",[961,34885,34886,34889],{},[996,34887,34888],{},"DJ software"," – built-in proprietary algorithms",[961,34891,34892,34895],{},[996,34893,34894],{},"Academic"," – research papers, not production services",[842,34897,34898],{},"This leaves open source as the primary option for structure analysis.",[1074,34900,34902],{"id":34901},"the-open-source-mir-maintenance-problem","The Open Source MIR Maintenance Problem",[842,34904,34905,34906,34909],{},"Many MIR libraries suffer from a common pattern: ",[996,34907,34908],{},"written once, then abandoned",". Here's why:",[871,34911,34912,34920],{},[874,34913,34914],{},[877,34915,34916,34918],{},[880,34917,33978],{},[880,34919,33981],{},[887,34921,34922,34932,34942,34952,34966],{},[877,34923,34924,34929],{},[892,34925,34926],{},[996,34927,34928],{},"Academic origins",[892,34930,34931],{},"Most MIR tools (madmom, MSAF, allin1) come from PhD research. When the researcher graduates or changes projects, development stops.",[877,34933,34934,34939],{},[892,34935,34936],{},[996,34937,34938],{},"Publication vs. maintenance",[892,34940,34941],{},"In academia, publishing a paper = success. Maintaining software ≠ academic credit.",[877,34943,34944,34949],{},[892,34945,34946],{},[996,34947,34948],{},"Niche community",[892,34950,34951],{},"Small user base means few contributors and little pressure to fix bugs.",[877,34953,34954,34959],{},[892,34955,34956],{},[996,34957,34958],{},"Rapid Python/ML changes",[892,34960,34961,34962,34965],{},"Python 3.10 removed ",[895,34963,34964],{},"collections.MutableSequence",", PyTorch changes API constantly. Keeping up requires active maintenance.",[877,34967,34968,34973],{},[892,34969,34970],{},[996,34971,34972],{},"No commercial sponsorship",[892,34974,34975],{},"Unlike web frameworks (React/Meta, Angular/Google), MIR tools have no corporate backing.",[842,34977,34978],{},[996,34979,34980],{},"Real examples:",[871,34982,34983,34995],{},[874,34984,34985],{},[877,34986,34987,34990,34993],{},[880,34988,34989],{},"Project",[880,34991,34992],{},"Last PyPI Release",[880,34994,14931],{},[887,34996,34997,35007,35017],{},[877,34998,34999,35001,35004],{},[892,35000,34112],{},[892,35002,35003],{},"2019",[892,35005,35006],{},"Uses deprecated Python APIs",[877,35008,35009,35011,35014],{},[892,35010,31962],{},[892,35012,35013],{},"2020",[892,35015,35016],{},"Minimal maintenance",[877,35018,35019,35021,35023],{},[892,35020,31944],{},[892,35022,12503],{},[892,35024,35025],{},"Active, but blocked by natten/madmom issues",[842,35027,35028],{},"This is why MTL Audio Locators defaults to librosa – it's the only dependency that's actively maintained and works across all Python versions.",[1074,35030,35032],{"id":35031},"source-separation-preprocessing","Source Separation (Preprocessing)",[871,35034,35035,35045],{},[874,35036,35037],{},[877,35038,35039,35041,35043],{},[880,35040,11370],{},[880,35042,3590],{},[880,35044,2719],{},[887,35046,35047,35064],{},[877,35048,35049,35055,35061],{},[892,35050,35051,35054],{},[996,35052,35053],{},"Demucs"," (Meta)",[892,35056,35057],{},[846,35058,15320],{"href":35059,"rel":35060},"https://github.com/facebookresearch/demucs",[850],[892,35062,35063],{},"Best quality, used by allin1",[877,35065,35066,35072,35078],{},[892,35067,35068,35071],{},[996,35069,35070],{},"Spleeter"," (Deezer)",[892,35073,35074],{},[846,35075,15320],{"href":35076,"rel":35077},"https://github.com/deezer/spleeter",[850],[892,35079,35080],{},"Faster, good for batch processing",[4937,35082],{},[863,35084,27869],{"id":27868},[958,35086,35087,35093,35098],{},[961,35088,35089,35092],{},[846,35090,35091],{"href":137},"What Data Can We Extract from Ableton's .als and .asd Files"," – Parsing Ableton project files",[961,35094,35095,35097],{},[846,35096,27886],{"href":459}," – Manual section marking",[961,35099,35100,35103],{},[846,35101,35102],{"href":201},"Why We Decided to Use WaveSurfer"," – Audio visualization in the browser",[4937,35105],{},[1032,35107,35108],{},[842,35109,35110,35112,35113,31765],{},[996,35111,31759],{}," MTL Audio Locators is available on ",[846,35114,15320],{"href":34561,"rel":35115},[850],[1680,35117,35118],{},"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 .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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":728,"searchDepth":729,"depth":729,"links":35120},[35121,35122,35127,35128,35129,35135,35136,35141,35142,35147,35148,35156],{"id":31839,"depth":729,"text":31840},{"id":31913,"depth":729,"text":31914,"children":35123},[35124,35125,35126],{"id":31993,"depth":1112,"text":31994},{"id":32157,"depth":1112,"text":32158},{"id":32256,"depth":1112,"text":32257},{"id":32322,"depth":729,"text":32323},{"id":16139,"depth":729,"text":32460},{"id":32835,"depth":729,"text":32836,"children":35130},[35131,35132,35133,35134],{"id":8509,"depth":1112,"text":8510},{"id":32869,"depth":1112,"text":32870},{"id":32921,"depth":1112,"text":32922},{"id":33174,"depth":1112,"text":33175},{"id":33426,"depth":729,"text":33427},{"id":30879,"depth":729,"text":30880,"children":35137},[35138,35139,35140],{"id":33613,"depth":1112,"text":33614},{"id":33916,"depth":1112,"text":33917},{"id":33940,"depth":1112,"text":33941},{"id":33965,"depth":729,"text":33966},{"id":34061,"depth":729,"text":34062,"children":35143},[35144,35145,35146],{"id":34065,"depth":1112,"text":34066},{"id":34183,"depth":1112,"text":34184},{"id":34465,"depth":1112,"text":34466},{"id":31658,"depth":729,"text":31659},{"id":34608,"depth":729,"text":34609,"children":35149},[35150,35151,35152,35153,35154,35155],{"id":34615,"depth":1112,"text":34616},{"id":34712,"depth":1112,"text":34713},{"id":34780,"depth":1112,"text":34781},{"id":34871,"depth":1112,"text":34872},{"id":34901,"depth":1112,"text":34902},{"id":35031,"depth":1112,"text":35032},{"id":27868,"depth":729,"text":27869},"2026-01-29T00:00:00.000Z","A technical look at automatic song structure detection using AI and signal processing. Identify song sections with MTL Audio Locators.",{"src":32832},{"enabled":738,"items":35161},[35162,35164,35166,35168],{"text":35163,"icon":11614},"Three analysis backends (allin1, MSAF, librosa) auto-select the best available engine.",{"text":35165,"icon":9547},"Output uses a consistent labeling convention (VRS1, CHO1, BRG1) designed for DAW import.",{"text":35167,"icon":5365},"Includes a REST API, CLI, and Python API for integration into any workflow.",{"text":35169,"icon":8500},"The librosa fallback works reliably on all platforms despite allin1 dependency issues.",{},{"title":100,"description":35158},[14100,731,27927],"Uuc7eBWbp0AzznCW1MhGn_O--FeZDiA7QrG4g3zdaVc",{"id":35175,"title":406,"authors":35176,"badge":35179,"body":35181,"category":756,"client":723,"date":37686,"description":37687,"extension":734,"faq":723,"featured":738,"featuredOrder":3409,"hidden":69,"image":37688,"keyTakeaways":37690,"meta":37701,"navigation":738,"path":407,"seo":37702,"status":723,"stem":408,"tags":37703,"teaser":723,"__hash__":37704},"posts/blog/software-development/connecting-your-max-for-live-device-to-a-cloud-api.md",[35177],{"name":834,"to":720,"avatar":35178},{"src":722},{"label":35180,"color":32438},"MusicTech",{"type":725,"value":35182,"toc":37650},[35183,35185,35190,35197,35214,35216,35220,35223,35277,35280,35282,35286,35299,35312,35319,35333,35337,35390,35392,35396,35399,35403,35406,35411,35676,35680,35862,35866,35877,35879,35883,35886,35890,35909,35913,35916,36393,36403,36405,36409,36416,36422,36425,36431,36436,36438,36442,36447,36801,36810,36812,36816,36819,37144,37149,37171,37173,37177,37184,37190,37192,37254,37258,37261,37270,37273,37275,37279,37283,37286,37302,37306,37309,37325,37329,37332,37348,37350,37402,37404,37407,37417,37451,37454,37456,37460,37464,37487,37491,37512,37514,37516,37519,37579,37581,37583,37586,37617,37619,37621,37633,37635,37637,37640,37647],[863,35184,27940],{"id":27939},[842,35186,12910,35187,35189],{},[846,35188,12913],{"href":459},", we built a Max for Live device that exports arrangement locators to JSON. The data stayed local - displayed in the device UI or saved to a file.",[842,35191,35192,35193,35196],{},"Now let's take it further: ",[996,35194,35195],{},"sending that data to a cloud API"," where it can be stored, visualized, and integrated with other tools.",[1045,35198,35200],{"className":35199},[13033,13034],[1045,35201,35207],{"className":35202},[35203,35204,35205,35206],"bg-neutral-800","p-4","rounded-xl","max-w-md",[842,35208,35209],{},[1027,35210],{"alt":35211,"src":35212,"className":35213},"Ableton Live with Max for Live device sending data to Cloud API","/images/blog/musictechlab_blog_ableton-api-full-integration.webp",[8189],[4937,35215],{},[863,35217,35219],{"id":35218},"why-connect-to-an-api","Why Connect to an API?",[842,35221,35222],{},"Local JSON files are useful, but they have limitations:",[871,35224,35225,35235],{},[874,35226,35227],{},[877,35228,35229,35232],{},[880,35230,35231],{},"Local Files",[880,35233,35234],{},"Cloud API",[887,35236,35237,35245,35253,35261,35269],{},[877,35238,35239,35242],{},[892,35240,35241],{},"Manual file management",[892,35243,35244],{},"Automatic storage",[877,35246,35247,35250],{},[892,35248,35249],{},"Single machine access",[892,35251,35252],{},"Access from anywhere",[877,35254,35255,35258],{},[892,35256,35257],{},"No version history",[892,35259,35260],{},"Full export history",[877,35262,35263,35266],{},[892,35264,35265],{},"No visualization",[892,35267,35268],{},"Browser-based timeline view",[877,35270,35271,35274],{},[892,35272,35273],{},"Manual sharing",[892,35275,35276],{},"Team collaboration",[842,35278,35279],{},"By connecting your Max for Live device to an API, every export becomes part of a searchable, visualized database.",[4937,35281],{},[863,35283,35285],{"id":35284},"architecture-overview-c4-model","Architecture Overview (C4 Model)",[842,35287,35288,35289,35294,35295,35298],{},"We use the ",[846,35290,35293],{"href":35291,"rel":35292},"https://c4model.com/",[850],"C4 model"," to document the system architecture. Here's the ",[996,35296,35297],{},"System Landscape"," showing all actors and systems:",[1045,35300,35302],{"className":35301},[13033,13034],[1045,35303,35305],{"className":35304},[35203,35204,35205,35206],[842,35306,35307],{},[1027,35308],{"alt":35309,"src":35310,"className":35311},"C4 System Landscape - Music Producer, Ableton Live, and Export API","/images/blog/musictechlab_blog_ableton-api-c4-landscape.webp",[8189],[842,35313,35314,35315,35318],{},"And the ",[996,35316,35317],{},"Container View"," showing internal components:",[1045,35320,35322],{"className":35321},[13033,13034],[1045,35323,35326],{"className":35324},[35203,35204,35205,35325],"max-w-xs",[842,35327,35328],{},[1027,35329],{"alt":35330,"src":35331,"className":35332},"C4 Container View - Max for Live Device, Export API, Database","/images/blog/musictechlab_blog_ableton-api-c4-containers.webp",[8189],[1074,35334,35336],{"id":35335},"key-components","Key Components",[871,35338,35339,35350],{},[874,35340,35341],{},[877,35342,35343,35346,35348],{},[880,35344,35345],{},"Container",[880,35347,26271],{},[880,35349,16942],{},[887,35351,35352,35365,35378],{},[877,35353,35354,35359,35362],{},[892,35355,35356],{},[996,35357,35358],{},"Max for Live Device",[892,35360,35361],{},"JavaScript, Node for Max",[892,35363,35364],{},"Extracts metadata from Ableton, sends to API",[877,35366,35367,35372,35375],{},[892,35368,35369],{},[996,35370,35371],{},"Export API",[892,35373,35374],{},"FastAPI, Cloud Run",[892,35376,35377],{},"Receives, validates, stores exports",[877,35379,35380,35384,35387],{},[892,35381,35382],{},[996,35383,2169],{},[892,35385,35386],{},"PostgreSQL (Cloud SQL)",[892,35388,35389],{},"Persistent storage with JSON sections",[4937,35391],{},[863,35393,35395],{"id":35394},"the-api-receiving-ableton-exports","The API: Receiving Ableton Exports",[842,35397,35398],{},"Our API is built with FastAPI and deployed on Google Cloud Run. Here's the core endpoint:",[1074,35400,35402],{"id":35401},"post-apiabletonexports","POST /api/ableton/exports",[842,35404,35405],{},"Receives and stores project metadata from Ableton Live.",[842,35407,35408],{},[996,35409,35410],{},"Request Schema:",[1013,35412,35414],{"className":1136,"code":35413,"language":1139,"meta":728,"style":728},"{\n  \"project\": \"my_track\",\n  \"bpm\": 103.34,\n  \"time_signature\": {\n    \"numerator\": 4,\n    \"denominator\": 4\n  },\n  \"downbeat_seconds\": 0,\n  \"sections\": [\n    { \"label\": \"INTRO\", \"time_seconds\": 0 },\n    { \"label\": \"VERSE\", \"time_seconds\": 16 },\n    { \"label\": \"CHORUS\", \"time_seconds\": 48 }\n  ],\n  \"exported_at\": \"2026-01-27T14:30:00Z\",\n  \"file_path\": \"/Users/producer/Music/my_track.als\"\n}\n",[895,35415,35416,35420,35440,35455,35468,35483,35497,35501,35516,35528,35562,35596,35630,35634,35654,35672],{"__ignoreMap":728},[1086,35417,35418],{"class":1088,"line":1089},[1086,35419,1147],{"class":1146},[1086,35421,35422,35424,35427,35429,35431,35433,35436,35438],{"class":1088,"line":729},[1086,35423,1152],{"class":1146},[1086,35425,35426],{"class":1155},"project",[1086,35428,1159],{"class":1146},[1086,35430,1133],{"class":1146},[1086,35432,1195],{"class":1146},[1086,35434,35435],{"class":1096},"my_track",[1086,35437,1159],{"class":1146},[1086,35439,1202],{"class":1146},[1086,35441,35442,35444,35446,35448,35450,35453],{"class":1088,"line":1112},[1086,35443,1152],{"class":1146},[1086,35445,31015],{"class":1155},[1086,35447,1159],{"class":1146},[1086,35449,1133],{"class":1146},[1086,35451,35452],{"class":1187}," 103.34",[1086,35454,1202],{"class":1146},[1086,35456,35457,35459,35462,35464,35466],{"class":1088,"line":1181},[1086,35458,1152],{"class":1146},[1086,35460,35461],{"class":1155},"time_signature",[1086,35463,1159],{"class":1146},[1086,35465,1133],{"class":1146},[1086,35467,1164],{"class":1146},[1086,35469,35470,35472,35475,35477,35479,35481],{"class":1088,"line":1205},[1086,35471,1169],{"class":1146},[1086,35473,35474],{"class":1092},"numerator",[1086,35476,1159],{"class":1146},[1086,35478,1133],{"class":1146},[1086,35480,30711],{"class":1187},[1086,35482,1202],{"class":1146},[1086,35484,35485,35487,35490,35492,35494],{"class":1088,"line":1276},[1086,35486,1169],{"class":1146},[1086,35488,35489],{"class":1092},"denominator",[1086,35491,1159],{"class":1146},[1086,35493,1133],{"class":1146},[1086,35495,35496],{"class":1187}," 4\n",[1086,35498,35499],{"class":1088,"line":1282},[1086,35500,27571],{"class":1146},[1086,35502,35503,35505,35508,35510,35512,35514],{"class":1088,"line":1288},[1086,35504,1152],{"class":1146},[1086,35506,35507],{"class":1155},"downbeat_seconds",[1086,35509,1159],{"class":1146},[1086,35511,1133],{"class":1146},[1086,35513,22718],{"class":1187},[1086,35515,1202],{"class":1146},[1086,35517,35518,35520,35522,35524,35526],{"class":1088,"line":2685},[1086,35519,1152],{"class":1146},[1086,35521,30324],{"class":1155},[1086,35523,1159],{"class":1146},[1086,35525,1133],{"class":1146},[1086,35527,6580],{"class":1146},[1086,35529,35530,35532,35534,35536,35538,35540,35542,35545,35547,35549,35551,35554,35556,35558,35560],{"class":1088,"line":2700},[1086,35531,30335],{"class":1146},[1086,35533,1195],{"class":1146},[1086,35535,10840],{"class":1092},[1086,35537,1159],{"class":1146},[1086,35539,1133],{"class":1146},[1086,35541,1195],{"class":1146},[1086,35543,35544],{"class":1096},"INTRO",[1086,35546,1159],{"class":1146},[1086,35548,1227],{"class":1146},[1086,35550,1195],{"class":1146},[1086,35552,35553],{"class":1092},"time_seconds",[1086,35555,1159],{"class":1146},[1086,35557,1133],{"class":1146},[1086,35559,22718],{"class":1187},[1086,35561,4792],{"class":1146},[1086,35563,35564,35566,35568,35570,35572,35574,35576,35579,35581,35583,35585,35587,35589,35591,35594],{"class":1088,"line":3398},[1086,35565,30335],{"class":1146},[1086,35567,1195],{"class":1146},[1086,35569,10840],{"class":1092},[1086,35571,1159],{"class":1146},[1086,35573,1133],{"class":1146},[1086,35575,1195],{"class":1146},[1086,35577,35578],{"class":1096},"VERSE",[1086,35580,1159],{"class":1146},[1086,35582,1227],{"class":1146},[1086,35584,1195],{"class":1146},[1086,35586,35553],{"class":1092},[1086,35588,1159],{"class":1146},[1086,35590,1133],{"class":1146},[1086,35592,35593],{"class":1187}," 16",[1086,35595,4792],{"class":1146},[1086,35597,35598,35600,35602,35604,35606,35608,35610,35613,35615,35617,35619,35621,35623,35625,35628],{"class":1088,"line":1715},[1086,35599,30335],{"class":1146},[1086,35601,1195],{"class":1146},[1086,35603,10840],{"class":1092},[1086,35605,1159],{"class":1146},[1086,35607,1133],{"class":1146},[1086,35609,1195],{"class":1146},[1086,35611,35612],{"class":1096},"CHORUS",[1086,35614,1159],{"class":1146},[1086,35616,1227],{"class":1146},[1086,35618,1195],{"class":1146},[1086,35620,35553],{"class":1092},[1086,35622,1159],{"class":1146},[1086,35624,1133],{"class":1146},[1086,35626,35627],{"class":1187}," 48",[1086,35629,27132],{"class":1146},[1086,35631,35632],{"class":1088,"line":3409},[1086,35633,31190],{"class":1146},[1086,35635,35636,35638,35641,35643,35645,35647,35650,35652],{"class":1088,"line":3415},[1086,35637,1152],{"class":1146},[1086,35639,35640],{"class":1155},"exported_at",[1086,35642,1159],{"class":1146},[1086,35644,1133],{"class":1146},[1086,35646,1195],{"class":1146},[1086,35648,35649],{"class":1096},"2026-01-27T14:30:00Z",[1086,35651,1159],{"class":1146},[1086,35653,1202],{"class":1146},[1086,35655,35656,35658,35661,35663,35665,35667,35670],{"class":1088,"line":3421},[1086,35657,1152],{"class":1146},[1086,35659,35660],{"class":1155},"file_path",[1086,35662,1159],{"class":1146},[1086,35664,1133],{"class":1146},[1086,35666,1195],{"class":1146},[1086,35668,35669],{"class":1096},"/Users/producer/Music/my_track.als",[1086,35671,4441],{"class":1146},[1086,35673,35674],{"class":1088,"line":3427},[1086,35675,1291],{"class":1146},[842,35677,35678],{},[996,35679,33075],{},[1013,35681,35683],{"className":1136,"code":35682,"language":1139,"meta":728,"style":728},"{\n  \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"project_name\": \"my_track\",\n  \"bpm\": 103.34,\n  \"time_signature\": { \"numerator\": 4, \"denominator\": 4 },\n  \"downbeat_seconds\": 0,\n  \"sections\": [...],\n  \"exported_at\": \"2026-01-27T14:30:00Z\",\n  \"created_at\": \"2026-01-27T14:30:05Z\",\n  \"is_duplicate\": false\n}\n",[895,35684,35685,35689,35708,35727,35741,35777,35791,35807,35825,35844,35858],{"__ignoreMap":728},[1086,35686,35687],{"class":1088,"line":1089},[1086,35688,1147],{"class":1146},[1086,35690,35691,35693,35695,35697,35699,35701,35704,35706],{"class":1088,"line":729},[1086,35692,1152],{"class":1146},[1086,35694,4156],{"class":1155},[1086,35696,1159],{"class":1146},[1086,35698,1133],{"class":1146},[1086,35700,1195],{"class":1146},[1086,35702,35703],{"class":1096},"550e8400-e29b-41d4-a716-446655440000",[1086,35705,1159],{"class":1146},[1086,35707,1202],{"class":1146},[1086,35709,35710,35712,35715,35717,35719,35721,35723,35725],{"class":1088,"line":1112},[1086,35711,1152],{"class":1146},[1086,35713,35714],{"class":1155},"project_name",[1086,35716,1159],{"class":1146},[1086,35718,1133],{"class":1146},[1086,35720,1195],{"class":1146},[1086,35722,35435],{"class":1096},[1086,35724,1159],{"class":1146},[1086,35726,1202],{"class":1146},[1086,35728,35729,35731,35733,35735,35737,35739],{"class":1088,"line":1181},[1086,35730,1152],{"class":1146},[1086,35732,31015],{"class":1155},[1086,35734,1159],{"class":1146},[1086,35736,1133],{"class":1146},[1086,35738,35452],{"class":1187},[1086,35740,1202],{"class":1146},[1086,35742,35743,35745,35747,35749,35751,35753,35755,35757,35759,35761,35763,35765,35767,35769,35771,35773,35775],{"class":1088,"line":1205},[1086,35744,1152],{"class":1146},[1086,35746,35461],{"class":1155},[1086,35748,1159],{"class":1146},[1086,35750,1133],{"class":1146},[1086,35752,4520],{"class":1146},[1086,35754,1195],{"class":1146},[1086,35756,35474],{"class":1092},[1086,35758,1159],{"class":1146},[1086,35760,1133],{"class":1146},[1086,35762,30711],{"class":1187},[1086,35764,1227],{"class":1146},[1086,35766,1195],{"class":1146},[1086,35768,35489],{"class":1092},[1086,35770,1159],{"class":1146},[1086,35772,1133],{"class":1146},[1086,35774,30711],{"class":1187},[1086,35776,4792],{"class":1146},[1086,35778,35779,35781,35783,35785,35787,35789],{"class":1088,"line":1276},[1086,35780,1152],{"class":1146},[1086,35782,35507],{"class":1155},[1086,35784,1159],{"class":1146},[1086,35786,1133],{"class":1146},[1086,35788,22718],{"class":1187},[1086,35790,1202],{"class":1146},[1086,35792,35793,35795,35797,35799,35801,35803,35805],{"class":1088,"line":1282},[1086,35794,1152],{"class":1146},[1086,35796,30324],{"class":1155},[1086,35798,1159],{"class":1146},[1086,35800,1133],{"class":1146},[1086,35802,1217],{"class":1146},[1086,35804,33148],{"class":1436},[1086,35806,9297],{"class":1146},[1086,35808,35809,35811,35813,35815,35817,35819,35821,35823],{"class":1088,"line":1288},[1086,35810,1152],{"class":1146},[1086,35812,35640],{"class":1155},[1086,35814,1159],{"class":1146},[1086,35816,1133],{"class":1146},[1086,35818,1195],{"class":1146},[1086,35820,35649],{"class":1096},[1086,35822,1159],{"class":1146},[1086,35824,1202],{"class":1146},[1086,35826,35827,35829,35831,35833,35835,35837,35840,35842],{"class":1088,"line":2685},[1086,35828,1152],{"class":1146},[1086,35830,25289],{"class":1155},[1086,35832,1159],{"class":1146},[1086,35834,1133],{"class":1146},[1086,35836,1195],{"class":1146},[1086,35838,35839],{"class":1096},"2026-01-27T14:30:05Z",[1086,35841,1159],{"class":1146},[1086,35843,1202],{"class":1146},[1086,35845,35846,35848,35851,35853,35855],{"class":1088,"line":2700},[1086,35847,1152],{"class":1146},[1086,35849,35850],{"class":1155},"is_duplicate",[1086,35852,1159],{"class":1146},[1086,35854,1133],{"class":1146},[1086,35856,35857],{"class":1146}," false\n",[1086,35859,35860],{"class":1088,"line":3398},[1086,35861,1291],{"class":1146},[1074,35863,35865],{"id":35864},"idempotency","Idempotency",[842,35867,35868,35869,35872,35873,35876],{},"The API is ",[996,35870,35871],{},"idempotent"," - if you accidentally export the same project twice with the same timestamp, it returns the existing record with ",[895,35874,35875],{},"is_duplicate: true"," instead of creating a duplicate.",[4937,35878],{},[863,35880,35882],{"id":35881},"extending-the-max-for-live-device","Extending the Max for Live Device",[842,35884,35885],{},"Now let's modify our JavaScript to send data to the API.",[1074,35887,35889],{"id":35888},"the-challenge-max-for-live-http-requests","The Challenge: Max for Live HTTP Requests",[842,35891,35892,35893,28366,35896,35899,35900,28366,35903,35906,35907,861],{},"Max for Live's JavaScript environment doesn't have native ",[895,35894,35895],{},"fetch()",[895,35897,35898],{},"XMLHttpRequest",". Instead, we use the ",[895,35901,35902],{},"jweb",[895,35904,35905],{},"maxurl"," objects, or - the simplest approach - shell out to ",[895,35908,33044],{},[1074,35910,35912],{"id":35911},"core-logic-marker_exportjs","Core Logic (marker_export.js)",[842,35914,35915],{},"The JavaScript extends our previous export script with API communication:",[1013,35917,35919],{"className":33433,"code":35918,"language":33435,"meta":728,"style":728},"// Pseudo-code: marker_export.js\n\nfunction bang() {\n  // 1. Get project data from Live via LiveAPI\n  song = new LiveAPI(\"live_set\")\n\n  name = song.get(\"name\")           // Project name\n  bpm = song.get(\"tempo\")           // BPM\n  sigNum = song.get(\"signature_numerator\")\n  sigDen = song.get(\"signature_denominator\")\n\n  // 2. Collect all locators (cue points)\n  locatorIds = song.get(\"cue_points\")\n  sections = []\n\n  for each locatorId:\n    locator = new LiveAPI(\"id \" + locatorId)\n    sections.push({\n      label: locator.get(\"name\"),      // \"INTRO\", \"VERSE\", etc.\n      time_seconds: locator.get(\"time\") // Position in seconds\n    })\n\n  // 3. Build payload matching API schema\n  payload = {\n    project: name,\n    bpm: bpm,\n    time_signature: { numerator: sigNum, denominator: sigDen },\n    sections: sections,\n    exported_at: now()\n  }\n\n  // 4. Display in UI + send to API\n  outlet(0, JSON.stringify(payload))  // → textedit (preview)\n  outlet(1, payload)                   // → node.script (API sender)\n}\n",[895,35920,35921,35926,35930,35942,35947,35970,35974,36002,36029,36053,36077,36081,36086,36110,36119,36123,36136,36162,36176,36205,36231,36237,36241,36246,36255,36266,36278,36307,36318,36330,36334,36338,36343,36370,36389],{"__ignoreMap":728},[1086,35922,35923],{"class":1088,"line":1089},[1086,35924,35925],{"class":1427},"// Pseudo-code: marker_export.js\n",[1086,35927,35928],{"class":1088,"line":729},[1086,35929,3390],{"emptyLinePlaceholder":738},[1086,35931,35932,35935,35938,35940],{"class":1088,"line":1112},[1086,35933,35934],{"class":1155},"function",[1086,35936,35937],{"class":1105}," bang",[1086,35939,2516],{"class":1146},[1086,35941,1164],{"class":1146},[1086,35943,35944],{"class":1088,"line":1181},[1086,35945,35946],{"class":1427},"  // 1. Get project data from Live via LiveAPI\n",[1086,35948,35949,35952,35954,35956,35959,35961,35963,35966,35968],{"class":1088,"line":1205},[1086,35950,35951],{"class":1436},"  song",[1086,35953,19552],{"class":1146},[1086,35955,26591],{"class":1146},[1086,35957,35958],{"class":1105}," LiveAPI",[1086,35960,1398],{"class":4109},[1086,35962,1159],{"class":1146},[1086,35964,35965],{"class":1096},"live_set",[1086,35967,1159],{"class":1146},[1086,35969,1455],{"class":4109},[1086,35971,35972],{"class":1088,"line":1276},[1086,35973,3390],{"emptyLinePlaceholder":738},[1086,35975,35976,35979,35981,35984,35986,35988,35990,35992,35994,35996,35999],{"class":1088,"line":1282},[1086,35977,35978],{"class":1436},"  name",[1086,35980,19552],{"class":1146},[1086,35982,35983],{"class":1436}," song",[1086,35985,861],{"class":1146},[1086,35987,10812],{"class":1105},[1086,35989,1398],{"class":4109},[1086,35991,1159],{"class":1146},[1086,35993,4184],{"class":1096},[1086,35995,1159],{"class":1146},[1086,35997,35998],{"class":4109},")           ",[1086,36000,36001],{"class":1427},"// Project name\n",[1086,36003,36004,36007,36009,36011,36013,36015,36017,36019,36022,36024,36026],{"class":1088,"line":1288},[1086,36005,36006],{"class":1436},"  bpm",[1086,36008,19552],{"class":1146},[1086,36010,35983],{"class":1436},[1086,36012,861],{"class":1146},[1086,36014,10812],{"class":1105},[1086,36016,1398],{"class":4109},[1086,36018,1159],{"class":1146},[1086,36020,36021],{"class":1096},"tempo",[1086,36023,1159],{"class":1146},[1086,36025,35998],{"class":4109},[1086,36027,36028],{"class":1427},"// BPM\n",[1086,36030,36031,36034,36036,36038,36040,36042,36044,36046,36049,36051],{"class":1088,"line":2685},[1086,36032,36033],{"class":1436},"  sigNum",[1086,36035,19552],{"class":1146},[1086,36037,35983],{"class":1436},[1086,36039,861],{"class":1146},[1086,36041,10812],{"class":1105},[1086,36043,1398],{"class":4109},[1086,36045,1159],{"class":1146},[1086,36047,36048],{"class":1096},"signature_numerator",[1086,36050,1159],{"class":1146},[1086,36052,1455],{"class":4109},[1086,36054,36055,36058,36060,36062,36064,36066,36068,36070,36073,36075],{"class":1088,"line":2700},[1086,36056,36057],{"class":1436},"  sigDen",[1086,36059,19552],{"class":1146},[1086,36061,35983],{"class":1436},[1086,36063,861],{"class":1146},[1086,36065,10812],{"class":1105},[1086,36067,1398],{"class":4109},[1086,36069,1159],{"class":1146},[1086,36071,36072],{"class":1096},"signature_denominator",[1086,36074,1159],{"class":1146},[1086,36076,1455],{"class":4109},[1086,36078,36079],{"class":1088,"line":3398},[1086,36080,3390],{"emptyLinePlaceholder":738},[1086,36082,36083],{"class":1088,"line":1715},[1086,36084,36085],{"class":1427},"  // 2. Collect all locators (cue points)\n",[1086,36087,36088,36091,36093,36095,36097,36099,36101,36103,36106,36108],{"class":1088,"line":3409},[1086,36089,36090],{"class":1436},"  locatorIds",[1086,36092,19552],{"class":1146},[1086,36094,35983],{"class":1436},[1086,36096,861],{"class":1146},[1086,36098,10812],{"class":1105},[1086,36100,1398],{"class":4109},[1086,36102,1159],{"class":1146},[1086,36104,36105],{"class":1096},"cue_points",[1086,36107,1159],{"class":1146},[1086,36109,1455],{"class":4109},[1086,36111,36112,36115,36117],{"class":1088,"line":3415},[1086,36113,36114],{"class":1436},"  sections",[1086,36116,19552],{"class":1146},[1086,36118,5920],{"class":4109},[1086,36120,36121],{"class":1088,"line":3421},[1086,36122,3390],{"emptyLinePlaceholder":738},[1086,36124,36125,36128,36131,36134],{"class":1088,"line":3427},[1086,36126,36127],{"class":1436},"  for",[1086,36129,36130],{"class":1436}," each",[1086,36132,36133],{"class":1092}," locatorId",[1086,36135,1418],{"class":1146},[1086,36137,36138,36141,36143,36145,36147,36149,36151,36154,36156,36158,36160],{"class":1088,"line":3433},[1086,36139,36140],{"class":1436},"    locator",[1086,36142,19552],{"class":1146},[1086,36144,26591],{"class":1146},[1086,36146,35958],{"class":1105},[1086,36148,1398],{"class":4109},[1086,36150,1159],{"class":1146},[1086,36152,36153],{"class":1096},"id ",[1086,36155,1159],{"class":1146},[1086,36157,33787],{"class":1146},[1086,36159,36133],{"class":1436},[1086,36161,1455],{"class":4109},[1086,36163,36164,36167,36169,36172,36174],{"class":1088,"line":3439},[1086,36165,36166],{"class":1436},"    sections",[1086,36168,861],{"class":1146},[1086,36170,36171],{"class":1105},"push",[1086,36173,1398],{"class":4109},[1086,36175,1147],{"class":1146},[1086,36177,36178,36181,36183,36186,36188,36190,36192,36194,36196,36198,36200,36202],{"class":1088,"line":3444},[1086,36179,36180],{"class":4109},"      label",[1086,36182,1133],{"class":1146},[1086,36184,36185],{"class":1436}," locator",[1086,36187,861],{"class":1146},[1086,36189,10812],{"class":1105},[1086,36191,1398],{"class":4109},[1086,36193,1159],{"class":1146},[1086,36195,4184],{"class":1096},[1086,36197,1159],{"class":1146},[1086,36199,1410],{"class":4109},[1086,36201,1227],{"class":1146},[1086,36203,36204],{"class":1427},"      // \"INTRO\", \"VERSE\", etc.\n",[1086,36206,36207,36210,36212,36214,36216,36218,36220,36222,36224,36226,36228],{"class":1088,"line":3450},[1086,36208,36209],{"class":4109},"      time_seconds",[1086,36211,1133],{"class":1146},[1086,36213,36185],{"class":1436},[1086,36215,861],{"class":1146},[1086,36217,10812],{"class":1105},[1086,36219,1398],{"class":4109},[1086,36221,1159],{"class":1146},[1086,36223,31109],{"class":1096},[1086,36225,1159],{"class":1146},[1086,36227,2778],{"class":4109},[1086,36229,36230],{"class":1427},"// Position in seconds\n",[1086,36232,36233,36235],{"class":1088,"line":3456},[1086,36234,27075],{"class":1146},[1086,36236,1455],{"class":4109},[1086,36238,36239],{"class":1088,"line":3462},[1086,36240,3390],{"emptyLinePlaceholder":738},[1086,36242,36243],{"class":1088,"line":3467},[1086,36244,36245],{"class":1427},"  // 3. Build payload matching API schema\n",[1086,36247,36248,36251,36253],{"class":1088,"line":3473},[1086,36249,36250],{"class":1436},"  payload",[1086,36252,19552],{"class":1146},[1086,36254,1164],{"class":1146},[1086,36256,36257,36260,36262,36264],{"class":1088,"line":3479},[1086,36258,36259],{"class":4109},"    project",[1086,36261,1133],{"class":1146},[1086,36263,24696],{"class":1436},[1086,36265,1202],{"class":1146},[1086,36267,36268,36271,36273,36276],{"class":1088,"line":3485},[1086,36269,36270],{"class":4109},"    bpm",[1086,36272,1133],{"class":1146},[1086,36274,36275],{"class":1436}," bpm",[1086,36277,1202],{"class":1146},[1086,36279,36280,36283,36285,36287,36290,36292,36295,36297,36300,36302,36305],{"class":1088,"line":3491},[1086,36281,36282],{"class":4109},"    time_signature",[1086,36284,1133],{"class":1146},[1086,36286,4520],{"class":1146},[1086,36288,36289],{"class":4109}," numerator",[1086,36291,1133],{"class":1146},[1086,36293,36294],{"class":1436}," sigNum",[1086,36296,1227],{"class":1146},[1086,36298,36299],{"class":4109}," denominator",[1086,36301,1133],{"class":1146},[1086,36303,36304],{"class":1436}," sigDen",[1086,36306,4792],{"class":1146},[1086,36308,36309,36311,36313,36316],{"class":1088,"line":3497},[1086,36310,36166],{"class":4109},[1086,36312,1133],{"class":1146},[1086,36314,36315],{"class":1436}," sections",[1086,36317,1202],{"class":1146},[1086,36319,36320,36323,36325,36328],{"class":1088,"line":3503},[1086,36321,36322],{"class":4109},"    exported_at",[1086,36324,1133],{"class":1146},[1086,36326,36327],{"class":1105}," now",[1086,36329,1387],{"class":4109},[1086,36331,36332],{"class":1088,"line":3509},[1086,36333,1285],{"class":1146},[1086,36335,36336],{"class":1088,"line":3515},[1086,36337,3390],{"emptyLinePlaceholder":738},[1086,36339,36340],{"class":1088,"line":3520},[1086,36341,36342],{"class":1427},"  // 4. Display in UI + send to API\n",[1086,36344,36345,36348,36350,36352,36354,36356,36358,36360,36362,36364,36367],{"class":1088,"line":3526},[1086,36346,36347],{"class":1105},"  outlet",[1086,36349,1398],{"class":4109},[1086,36351,4417],{"class":1187},[1086,36353,1227],{"class":1146},[1086,36355,26986],{"class":1436},[1086,36357,861],{"class":1146},[1086,36359,26991],{"class":1105},[1086,36361,1398],{"class":4109},[1086,36363,21393],{"class":1436},[1086,36365,36366],{"class":4109},"))  ",[1086,36368,36369],{"class":1427},"// → textedit (preview)\n",[1086,36371,36372,36374,36376,36378,36380,36383,36386],{"class":1088,"line":3531},[1086,36373,36347],{"class":1105},[1086,36375,1398],{"class":4109},[1086,36377,4434],{"class":1187},[1086,36379,1227],{"class":1146},[1086,36381,36382],{"class":1436}," payload",[1086,36384,36385],{"class":4109},")                   ",[1086,36387,36388],{"class":1427},"// → node.script (API sender)\n",[1086,36390,36391],{"class":1088,"line":3537},[1086,36392,1291],{"class":1146},[842,36394,36395,36398,36399,36402],{},[996,36396,36397],{},"Key insight:"," LiveAPI returns arrays for most properties, so always handle ",[895,36400,36401],{},"Array.isArray()"," checks.",[4937,36404],{},[863,36406,36408],{"id":36407},"alternative-using-nodescript","Alternative: Using node.script",[842,36410,36411,36412,36415],{},"For a more robust solution, use the ",[895,36413,36414],{},"node.script"," object which runs a Node.js script directly within Max:",[842,36417,36418],{},[1027,36419],{"alt":36420,"src":36421},"Max for Live patcher with node.script for API communication","/images/blog/musictechlab_blog_ableton-api-max-patcher.webp",[842,36423,36424],{},"The patcher architecture:",[1013,36426,36429],{"className":36427,"code":36428,"language":1018},[1016],"┌─────────────────┐     ┌──────────────────────┐     ┌────────────────┐\n│   textbutton    │────▶│  js marker_export.js │────▶│   textedit     │\n│    \"Export\"     │     │                      │     │  (JSON output) │\n└─────────────────┘     └──────────┬───────────┘     └────────────────┘\n                                   │\n                                   ▼\n                        ┌──────────────────────────────────────┐\n                        │  node.script api_sender.js           │\n                        │  @autostart 1 @outlets 1             │\n                        └──────────────────┬───────────────────┘\n                                           │\n                                           ▼\n                                 ┌────────────────┐\n                                 │   textedit     │\n                                 │ (API status)   │\n                                 └────────────────┘\n",[895,36430,36428],{"__ignoreMap":728},[842,36432,5119,36433,36435],{},[895,36434,36414],{}," object runs a Node.js script within Max, providing full access to npm packages and modern JavaScript features.",[4937,36437],{},[863,36439,36441],{"id":36440},"the-api-sender-api_senderjs","The API Sender (api_sender.js)",[842,36443,5119,36444,36446],{},[895,36445,36414],{}," object runs a Node.js script that handles HTTP communication:",[1013,36448,36450],{"className":33433,"code":36449,"language":33435,"meta":728,"style":728},"// Pseudo-code: api_sender.js\n\nconst Max = require('max-api');\nconst API_URL = \"https://your-api.run.app/api/ableton/exports\";\n\nMax.addHandler(\"export\", async (jsonString) => {\n  // 1. Parse incoming data from marker_export.js\n  const payload = JSON.parse(jsonString);\n\n  // 2. POST to API\n  const response = await fetch(API_URL, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(payload)\n  });\n\n  // 3. Handle response\n  const result = await response.json();\n\n  if (result.is_duplicate) {\n    Max.outlet(\"status\", \"Already exported\");\n  } else {\n    Max.outlet(\"status\", \"Saved: \" + result.id);\n  }\n});\n",[895,36451,36452,36457,36461,36486,36504,36508,36541,36546,36568,36572,36577,36599,36613,36637,36655,36663,36667,36672,36692,36696,36712,36743,36752,36789,36793],{"__ignoreMap":728},[1086,36453,36454],{"class":1088,"line":1089},[1086,36455,36456],{"class":1427},"// Pseudo-code: api_sender.js\n",[1086,36458,36459],{"class":1088,"line":729},[1086,36460,3390],{"emptyLinePlaceholder":738},[1086,36462,36463,36465,36468,36470,36473,36475,36477,36480,36482,36484],{"class":1088,"line":1112},[1086,36464,33442],{"class":1155},[1086,36466,36467],{"class":1436}," Max ",[1086,36469,1440],{"class":1146},[1086,36471,36472],{"class":1105}," require",[1086,36474,1398],{"class":1436},[1086,36476,10742],{"class":1146},[1086,36478,36479],{"class":1096},"max-api",[1086,36481,10742],{"class":1146},[1086,36483,1410],{"class":1436},[1086,36485,33466],{"class":1146},[1086,36487,36488,36490,36493,36495,36497,36500,36502],{"class":1088,"line":1181},[1086,36489,33442],{"class":1155},[1086,36491,36492],{"class":1436}," API_URL ",[1086,36494,1440],{"class":1146},[1086,36496,1195],{"class":1146},[1086,36498,36499],{"class":1096},"https://your-api.run.app/api/ableton/exports",[1086,36501,1159],{"class":1146},[1086,36503,33466],{"class":1146},[1086,36505,36506],{"class":1088,"line":1205},[1086,36507,3390],{"emptyLinePlaceholder":738},[1086,36509,36510,36513,36515,36518,36520,36522,36524,36526,36528,36530,36532,36535,36537,36539],{"class":1088,"line":1276},[1086,36511,36512],{"class":1436},"Max",[1086,36514,861],{"class":1146},[1086,36516,36517],{"class":1105},"addHandler",[1086,36519,1398],{"class":1436},[1086,36521,1159],{"class":1146},[1086,36523,3625],{"class":1096},[1086,36525,1159],{"class":1146},[1086,36527,1227],{"class":1146},[1086,36529,4614],{"class":1155},[1086,36531,5979],{"class":1146},[1086,36533,36534],{"class":1401},"jsonString",[1086,36536,1410],{"class":1146},[1086,36538,26610],{"class":1155},[1086,36540,1164],{"class":1146},[1086,36542,36543],{"class":1088,"line":1282},[1086,36544,36545],{"class":1427},"  // 1. Parse incoming data from marker_export.js\n",[1086,36547,36548,36550,36552,36554,36556,36558,36560,36562,36564,36566],{"class":1088,"line":1288},[1086,36549,26450],{"class":1155},[1086,36551,36382],{"class":1436},[1086,36553,19552],{"class":1146},[1086,36555,26986],{"class":1436},[1086,36557,861],{"class":1146},[1086,36559,33508],{"class":1105},[1086,36561,1398],{"class":4109},[1086,36563,36534],{"class":1436},[1086,36565,1410],{"class":4109},[1086,36567,33466],{"class":1146},[1086,36569,36570],{"class":1088,"line":2685},[1086,36571,3390],{"emptyLinePlaceholder":738},[1086,36573,36574],{"class":1088,"line":2700},[1086,36575,36576],{"class":1427},"  // 2. POST to API\n",[1086,36578,36579,36581,36584,36586,36588,36590,36592,36595,36597],{"class":1088,"line":3398},[1086,36580,26450],{"class":1155},[1086,36582,36583],{"class":1436}," response",[1086,36585,19552],{"class":1146},[1086,36587,4659],{"class":1423},[1086,36589,26890],{"class":1105},[1086,36591,1398],{"class":4109},[1086,36593,36594],{"class":1436},"API_URL",[1086,36596,1227],{"class":1146},[1086,36598,1164],{"class":1146},[1086,36600,36601,36603,36605,36607,36609,36611],{"class":1088,"line":1715},[1086,36602,4762],{"class":4109},[1086,36604,1133],{"class":1146},[1086,36606,26405],{"class":1146},[1086,36608,4685],{"class":1096},[1086,36610,10742],{"class":1146},[1086,36612,1202],{"class":1146},[1086,36614,36615,36617,36619,36621,36623,36625,36627,36629,36631,36633,36635],{"class":1088,"line":3409},[1086,36616,17650],{"class":4109},[1086,36618,1133],{"class":1146},[1086,36620,4520],{"class":1146},[1086,36622,26405],{"class":1146},[1086,36624,20056],{"class":4109},[1086,36626,10742],{"class":1146},[1086,36628,1133],{"class":1146},[1086,36630,26405],{"class":1146},[1086,36632,20739],{"class":1096},[1086,36634,10742],{"class":1146},[1086,36636,4792],{"class":1146},[1086,36638,36639,36641,36643,36645,36647,36649,36651,36653],{"class":1088,"line":3415},[1086,36640,4777],{"class":4109},[1086,36642,1133],{"class":1146},[1086,36644,26986],{"class":1436},[1086,36646,861],{"class":1146},[1086,36648,26991],{"class":1105},[1086,36650,1398],{"class":4109},[1086,36652,21393],{"class":1436},[1086,36654,1455],{"class":4109},[1086,36656,36657,36659,36661],{"class":1088,"line":3421},[1086,36658,4797],{"class":1146},[1086,36660,1410],{"class":4109},[1086,36662,33466],{"class":1146},[1086,36664,36665],{"class":1088,"line":3427},[1086,36666,3390],{"emptyLinePlaceholder":738},[1086,36668,36669],{"class":1088,"line":3433},[1086,36670,36671],{"class":1427},"  // 3. Handle response\n",[1086,36673,36674,36676,36678,36680,36682,36684,36686,36688,36690],{"class":1088,"line":3439},[1086,36675,26450],{"class":1155},[1086,36677,23102],{"class":1436},[1086,36679,19552],{"class":1146},[1086,36681,4659],{"class":1423},[1086,36683,36583],{"class":1436},[1086,36685,861],{"class":1146},[1086,36687,1139],{"class":1105},[1086,36689,2516],{"class":4109},[1086,36691,33466],{"class":1146},[1086,36693,36694],{"class":1088,"line":3444},[1086,36695,3390],{"emptyLinePlaceholder":738},[1086,36697,36698,36700,36702,36704,36706,36708,36710],{"class":1088,"line":3450},[1086,36699,27233],{"class":1423},[1086,36701,5979],{"class":4109},[1086,36703,1473],{"class":1436},[1086,36705,861],{"class":1146},[1086,36707,35850],{"class":1436},[1086,36709,2778],{"class":4109},[1086,36711,1147],{"class":1146},[1086,36713,36714,36717,36719,36722,36724,36726,36728,36730,36732,36734,36737,36739,36741],{"class":1088,"line":3456},[1086,36715,36716],{"class":1436},"    Max",[1086,36718,861],{"class":1146},[1086,36720,36721],{"class":1105},"outlet",[1086,36723,1398],{"class":4109},[1086,36725,1159],{"class":1146},[1086,36727,11116],{"class":1096},[1086,36729,1159],{"class":1146},[1086,36731,1227],{"class":1146},[1086,36733,1195],{"class":1146},[1086,36735,36736],{"class":1096},"Already exported",[1086,36738,1159],{"class":1146},[1086,36740,1410],{"class":4109},[1086,36742,33466],{"class":1146},[1086,36744,36745,36747,36750],{"class":1088,"line":3462},[1086,36746,4797],{"class":1146},[1086,36748,36749],{"class":1423}," else",[1086,36751,1164],{"class":1146},[1086,36753,36754,36756,36758,36760,36762,36764,36766,36768,36770,36772,36775,36777,36779,36781,36783,36785,36787],{"class":1088,"line":3467},[1086,36755,36716],{"class":1436},[1086,36757,861],{"class":1146},[1086,36759,36721],{"class":1105},[1086,36761,1398],{"class":4109},[1086,36763,1159],{"class":1146},[1086,36765,11116],{"class":1096},[1086,36767,1159],{"class":1146},[1086,36769,1227],{"class":1146},[1086,36771,1195],{"class":1146},[1086,36773,36774],{"class":1096},"Saved: ",[1086,36776,1159],{"class":1146},[1086,36778,33787],{"class":1146},[1086,36780,23102],{"class":1436},[1086,36782,861],{"class":1146},[1086,36784,4156],{"class":1436},[1086,36786,1410],{"class":4109},[1086,36788,33466],{"class":1146},[1086,36790,36791],{"class":1088,"line":3473},[1086,36792,1285],{"class":1146},[1086,36794,36795,36797,36799],{"class":1088,"line":3479},[1086,36796,4423],{"class":1146},[1086,36798,1410],{"class":1436},[1086,36800,33466],{"class":1146},[842,36802,36803,36806,36807,36809],{},[996,36804,36805],{},"Why node.script?"," Unlike Max's built-in JS, ",[895,36808,36414],{}," provides full Node.js runtime with npm packages, async/await, and modern fetch API.",[4937,36811],{},[863,36813,36815],{"id":36814},"the-api-implementation","The API Implementation",[842,36817,36818],{},"The backend is a FastAPI service with PostgreSQL storage:",[1013,36820,36822],{"className":1368,"code":36821,"language":1250,"meta":728,"style":728},"# Pseudo-code: FastAPI endpoint\n\n@router.post(\"/ableton/exports\")\nasync def create_export(data: AbletonExportSchema, db: Session):\n\n    # 1. Idempotency check - prevent duplicates\n    existing = db.query(Export).filter(\n        project == data.project,\n        exported_at == data.exported_at\n    ).first()\n\n    if existing:\n        return { ...existing, is_duplicate: True }\n\n    # 2. Store new export\n    export = Export(\n        id = uuid4(),\n        project_name = data.project,\n        bpm = data.bpm,\n        sections = data.sections,    # JSON array\n        exported_at = data.exported_at\n    )\n\n    db.add(export)\n    db.commit()\n\n    return { ...export, is_duplicate: False }\n",[895,36823,36824,36829,36833,36855,36886,36890,36895,36919,36934,36948,36958,36962,36971,36992,36996,37001,37013,37024,37039,37054,37072,37085,37089,37093,37109,37120,37124],{"__ignoreMap":728},[1086,36825,36826],{"class":1088,"line":1089},[1086,36827,36828],{"class":1427},"# Pseudo-code: FastAPI endpoint\n",[1086,36830,36831],{"class":1088,"line":729},[1086,36832,3390],{"emptyLinePlaceholder":738},[1086,36834,36835,36837,36840,36842,36844,36846,36848,36851,36853],{"class":1088,"line":1112},[1086,36836,1376],{"class":1146},[1086,36838,36839],{"class":1105},"router",[1086,36841,861],{"class":1146},[1086,36843,7165],{"class":1105},[1086,36845,1398],{"class":1146},[1086,36847,1159],{"class":1146},[1086,36849,36850],{"class":1096},"/ableton/exports",[1086,36852,1159],{"class":1146},[1086,36854,1455],{"class":1146},[1086,36856,36857,36859,36862,36865,36867,36869,36871,36874,36876,36879,36881,36884],{"class":1088,"line":1181},[1086,36858,26419],{"class":1155},[1086,36860,36861],{"class":1155}," def",[1086,36863,36864],{"class":1105}," create_export",[1086,36866,1398],{"class":1146},[1086,36868,4337],{"class":1401},[1086,36870,1133],{"class":1146},[1086,36872,36873],{"class":1436}," AbletonExportSchema",[1086,36875,1227],{"class":1146},[1086,36877,36878],{"class":1401}," db",[1086,36880,1133],{"class":1146},[1086,36882,36883],{"class":1436}," Session",[1086,36885,4047],{"class":1146},[1086,36887,36888],{"class":1088,"line":1205},[1086,36889,3390],{"emptyLinePlaceholder":738},[1086,36891,36892],{"class":1088,"line":1276},[1086,36893,36894],{"class":1427},"    # 1. Idempotency check - prevent duplicates\n",[1086,36896,36897,36900,36902,36904,36906,36908,36910,36912,36914,36917],{"class":1088,"line":1282},[1086,36898,36899],{"class":1436},"    existing ",[1086,36901,1440],{"class":1146},[1086,36903,36878],{"class":1436},[1086,36905,861],{"class":1146},[1086,36907,7210],{"class":1105},[1086,36909,1398],{"class":1146},[1086,36911,3626],{"class":1105},[1086,36913,6786],{"class":1146},[1086,36915,36916],{"class":1105},"filter",[1086,36918,4094],{"class":1146},[1086,36920,36921,36924,36926,36928,36930,36932],{"class":1088,"line":1288},[1086,36922,36923],{"class":1105},"        project ",[1086,36925,6480],{"class":1146},[1086,36927,20125],{"class":1105},[1086,36929,861],{"class":1146},[1086,36931,35426],{"class":4109},[1086,36933,1202],{"class":1146},[1086,36935,36936,36939,36941,36943,36945],{"class":1088,"line":2685},[1086,36937,36938],{"class":1105},"        exported_at ",[1086,36940,6480],{"class":1146},[1086,36942,20125],{"class":1105},[1086,36944,861],{"class":1146},[1086,36946,36947],{"class":4109},"exported_at\n",[1086,36949,36950,36953,36956],{"class":1088,"line":2700},[1086,36951,36952],{"class":1146},"    ).",[1086,36954,36955],{"class":1105},"first",[1086,36957,1387],{"class":1146},[1086,36959,36960],{"class":1088,"line":3398},[1086,36961,3390],{"emptyLinePlaceholder":738},[1086,36963,36964,36966,36969],{"class":1088,"line":1715},[1086,36965,6474],{"class":1423},[1086,36967,36968],{"class":1436}," existing",[1086,36970,1418],{"class":1146},[1086,36972,36973,36975,36977,36980,36982,36985,36987,36990],{"class":1088,"line":3409},[1086,36974,4239],{"class":1423},[1086,36976,4520],{"class":1146},[1086,36978,36979],{"class":1436}," ...existing",[1086,36981,1227],{"class":1146},[1086,36983,36984],{"class":1436}," is_duplicate",[1086,36986,1133],{"class":1146},[1086,36988,36989],{"class":1146}," True",[1086,36991,27132],{"class":1146},[1086,36993,36994],{"class":1088,"line":3415},[1086,36995,3390],{"emptyLinePlaceholder":738},[1086,36997,36998],{"class":1088,"line":3421},[1086,36999,37000],{"class":1427},"    # 2. Store new export\n",[1086,37002,37003,37006,37008,37011],{"class":1088,"line":3427},[1086,37004,37005],{"class":1436},"    export ",[1086,37007,1440],{"class":1146},[1086,37009,37010],{"class":1105}," Export",[1086,37012,4094],{"class":1146},[1086,37014,37015,37017,37019,37022],{"class":1088,"line":3433},[1086,37016,22945],{"class":1401},[1086,37018,19552],{"class":1146},[1086,37020,37021],{"class":1105}," uuid4",[1086,37023,25628],{"class":1146},[1086,37025,37026,37029,37031,37033,37035,37037],{"class":1088,"line":3439},[1086,37027,37028],{"class":1401},"        project_name",[1086,37030,19552],{"class":1146},[1086,37032,20125],{"class":1105},[1086,37034,861],{"class":1146},[1086,37036,35426],{"class":4109},[1086,37038,1202],{"class":1146},[1086,37040,37041,37044,37046,37048,37050,37052],{"class":1088,"line":3444},[1086,37042,37043],{"class":1401},"        bpm",[1086,37045,19552],{"class":1146},[1086,37047,20125],{"class":1105},[1086,37049,861],{"class":1146},[1086,37051,31015],{"class":4109},[1086,37053,1202],{"class":1146},[1086,37055,37056,37059,37061,37063,37065,37067,37069],{"class":1088,"line":3450},[1086,37057,37058],{"class":1401},"        sections",[1086,37060,19552],{"class":1146},[1086,37062,20125],{"class":1105},[1086,37064,861],{"class":1146},[1086,37066,30324],{"class":4109},[1086,37068,1227],{"class":1146},[1086,37070,37071],{"class":1427},"    # JSON array\n",[1086,37073,37074,37077,37079,37081,37083],{"class":1088,"line":3456},[1086,37075,37076],{"class":1401},"        exported_at",[1086,37078,19552],{"class":1146},[1086,37080,20125],{"class":1105},[1086,37082,861],{"class":1146},[1086,37084,36947],{"class":4109},[1086,37086,37087],{"class":1088,"line":3462},[1086,37088,6219],{"class":1146},[1086,37090,37091],{"class":1088,"line":3467},[1086,37092,3390],{"emptyLinePlaceholder":738},[1086,37094,37095,37098,37100,37103,37105,37107],{"class":1088,"line":3473},[1086,37096,37097],{"class":1436},"    db",[1086,37099,861],{"class":1146},[1086,37101,37102],{"class":1105},"add",[1086,37104,1398],{"class":1146},[1086,37106,3625],{"class":1105},[1086,37108,1455],{"class":1146},[1086,37110,37111,37113,37115,37118],{"class":1088,"line":3479},[1086,37112,37097],{"class":1436},[1086,37114,861],{"class":1146},[1086,37116,37117],{"class":1105},"commit",[1086,37119,1387],{"class":1146},[1086,37121,37122],{"class":1088,"line":3485},[1086,37123,3390],{"emptyLinePlaceholder":738},[1086,37125,37126,37128,37130,37133,37135,37137,37139,37142],{"class":1088,"line":3491},[1086,37127,1460],{"class":1423},[1086,37129,4520],{"class":1146},[1086,37131,37132],{"class":1436}," ...export",[1086,37134,1227],{"class":1146},[1086,37136,36984],{"class":1436},[1086,37138,1133],{"class":1146},[1086,37140,37141],{"class":1146}," False",[1086,37143,27132],{"class":1146},[842,37145,37146],{},[996,37147,37148],{},"Key design decisions:",[958,37150,37151,37159,37165],{},[961,37152,37153,37155,37156],{},[996,37154,35865],{}," via unique constraint on ",[895,37157,37158],{},"(project_name, exported_at)",[961,37160,37161,37164],{},[996,37162,37163],{},"Sections stored as JSON"," for flexibility",[961,37166,37167,37170],{},[996,37168,37169],{},"UUID primary keys"," for distributed systems compatibility",[4937,37172],{},[863,37174,37176],{"id":37175},"browser-ui-visualizing-your-exports","Browser UI: Visualizing Your Exports",[842,37178,37179,37180,37183],{},"The API includes a browser-based UI at ",[895,37181,37182],{},"/browser"," that shows all your exports:",[842,37185,37186],{},[1027,37187],{"alt":37188,"src":37189},"Browser UI with timeline visualization of Ableton exports","/images/blog/musictechlab_blog_ableton-api-browser-ui.webp",[1074,37191,34800],{"id":33369},[871,37193,37194,37202],{},[874,37195,37196],{},[877,37197,37198,37200],{},[880,37199,1974],{},[880,37201,7624],{},[887,37203,37204,37214,37224,37234,37244],{},[877,37205,37206,37211],{},[892,37207,37208],{},[996,37209,37210],{},"Timeline visualization",[892,37212,37213],{},"Ableton-style arrangement view",[877,37215,37216,37221],{},[892,37217,37218],{},[996,37219,37220],{},"Filters",[892,37222,37223],{},"By project, BPM range, section count",[877,37225,37226,37231],{},[892,37227,37228],{},[996,37229,37230],{},"Search",[892,37232,37233],{},"Find exports by name",[877,37235,37236,37241],{},[892,37237,37238],{},[996,37239,37240],{},"Pagination",[892,37242,37243],{},"Handle large export histories",[877,37245,37246,37251],{},[892,37247,37248],{},[996,37249,37250],{},"JSON inspection",[892,37252,37253],{},"Click to see full export data",[1074,37255,37257],{"id":37256},"timeline-rendering","Timeline Rendering",[842,37259,37260],{},"Each export is visualized as a horizontal timeline with color-coded sections:",[1045,37262,31345,37264],{"className":37263},[13033,13034],[1027,37265],{"src":37266,"alt":37267,"className":37268},"/images/blog/musictechlab_blog_ableton-api-timeline-row.webp","Timeline visualization with color-coded sections",[37269],"max-w-[70%]",[842,37271,37272],{},"The visualization is generated client-side using vanilla JavaScript and CSS Grid, making it lightweight and fast.",[4937,37274],{},[863,37276,37278],{"id":37277},"additional-api-endpoints","Additional API Endpoints",[1074,37280,37282],{"id":37281},"get-apiabletonexports","GET /api/ableton/exports",[842,37284,37285],{},"List all exports with pagination and filtering:",[1013,37287,37289],{"className":1080,"code":37288,"language":1082,"meta":728,"style":728},"curl \"https://api.example.com/api/ableton/exports?project=my_track&page=1&limit=20\"\n",[895,37290,37291],{"__ignoreMap":728},[1086,37292,37293,37295,37297,37300],{"class":1088,"line":1089},[1086,37294,33044],{"class":1092},[1086,37296,1195],{"class":1146},[1086,37298,37299],{"class":1096},"https://api.example.com/api/ableton/exports?project=my_track&page=1&limit=20",[1086,37301,4441],{"class":1146},[1074,37303,37305],{"id":37304},"get-apiabletonexportsid","GET /api/ableton/exports/{id}",[842,37307,37308],{},"Get a specific export by ID:",[1013,37310,37312],{"className":1080,"code":37311,"language":1082,"meta":728,"style":728},"curl \"https://api.example.com/api/ableton/exports/550e8400-e29b-41d4-a716-446655440000\"\n",[895,37313,37314],{"__ignoreMap":728},[1086,37315,37316,37318,37320,37323],{"class":1088,"line":1089},[1086,37317,33044],{"class":1092},[1086,37319,1195],{"class":1146},[1086,37321,37322],{"class":1096},"https://api.example.com/api/ableton/exports/550e8400-e29b-41d4-a716-446655440000",[1086,37324,4441],{"class":1146},[1074,37326,37328],{"id":37327},"get-apiabletonprojects","GET /api/ableton/projects",[842,37330,37331],{},"Get list of all unique project names:",[1013,37333,37335],{"className":1080,"code":37334,"language":1082,"meta":728,"style":728},"curl \"https://api.example.com/api/ableton/projects\"\n",[895,37336,37337],{"__ignoreMap":728},[1086,37338,37339,37341,37343,37346],{"class":1088,"line":1089},[1086,37340,33044],{"class":1092},[1086,37342,1195],{"class":1146},[1086,37344,37345],{"class":1096},"https://api.example.com/api/ableton/projects",[1086,37347,4441],{"class":1146},[842,37349,33075],{},[1013,37351,37353],{"className":1136,"code":37352,"language":1139,"meta":728,"style":728},"{\n  \"projects\": [\"my_track\", \"remix_v2\", \"collab_final\"]\n}\n",[895,37354,37355,37359,37398],{"__ignoreMap":728},[1086,37356,37357],{"class":1088,"line":1089},[1086,37358,1147],{"class":1146},[1086,37360,37361,37363,37366,37368,37370,37372,37374,37376,37378,37380,37382,37385,37387,37389,37391,37394,37396],{"class":1088,"line":729},[1086,37362,1152],{"class":1146},[1086,37364,37365],{"class":1155},"projects",[1086,37367,1159],{"class":1146},[1086,37369,1133],{"class":1146},[1086,37371,1217],{"class":1146},[1086,37373,1159],{"class":1146},[1086,37375,35435],{"class":1096},[1086,37377,1159],{"class":1146},[1086,37379,1227],{"class":1146},[1086,37381,1195],{"class":1146},[1086,37383,37384],{"class":1096},"remix_v2",[1086,37386,1159],{"class":1146},[1086,37388,1227],{"class":1146},[1086,37390,1195],{"class":1146},[1086,37392,37393],{"class":1096},"collab_final",[1086,37395,1159],{"class":1146},[1086,37397,1273],{"class":1146},[1086,37399,37400],{"class":1088,"line":1112},[1086,37401,1291],{"class":1146},[4937,37403],{},[863,37405,1992],{"id":37406},"deployment",[842,37408,37409,37410,37413,37414,1133],{},"The API runs on ",[996,37411,37412],{},"Google Cloud Run"," with ",[996,37415,37416],{},"Cloud SQL (PostgreSQL)",[871,37418,37419,37428],{},[874,37420,37421],{},[877,37422,37423,37426],{},[880,37424,37425],{},"Component",[880,37427,34797],{},[887,37429,37430,37437,37444],{},[877,37431,37432,37434],{},[892,37433,11843],{},[892,37435,37436],{},"Cloud Run (serverless containers)",[877,37438,37439,37441],{},[892,37440,2169],{},[892,37442,37443],{},"Cloud SQL PostgreSQL",[877,37445,37446,37448],{},[892,37447,7791],{},[892,37449,37450],{},"europe-west1",[842,37452,37453],{},"Local development uses Docker Compose with the same PostgreSQL setup, ensuring dev/prod parity.",[4937,37455],{},[863,37457,37459],{"id":37458},"testing-the-integration","Testing the Integration",[1074,37461,37463],{"id":37462},"from-ableton","From Ableton",[991,37465,37466,37469,37472,37478,37484],{},[961,37467,37468],{},"Open a project with arrangement locators",[961,37470,37471],{},"Add the Max for Live device to any MIDI track",[961,37473,37474,37475],{},"Click ",[996,37476,37477],{},"\"Export\"",[961,37479,37480,37481,1159],{},"Watch the status panel - should show \"Saved: ",[1086,37482,37483],{},"uuid",[961,37485,37486],{},"Open Browser UI - your export appears in the timeline",[1074,37488,37490],{"id":37489},"verify-with-curl","Verify with curl",[1013,37492,37494],{"className":1080,"code":37493,"language":1082,"meta":728,"style":728},"curl \"https://your-api.run.app/api/ableton/projects\"\n# Returns: { \"projects\": [\"your_project_name\", ...] }\n",[895,37495,37496,37507],{"__ignoreMap":728},[1086,37497,37498,37500,37502,37505],{"class":1088,"line":1089},[1086,37499,33044],{"class":1092},[1086,37501,1195],{"class":1146},[1086,37503,37504],{"class":1096},"https://your-api.run.app/api/ableton/projects",[1086,37506,4441],{"class":1146},[1086,37508,37509],{"class":1088,"line":729},[1086,37510,37511],{"class":1427},"# Returns: { \"projects\": [\"your_project_name\", ...] }\n",[4937,37513],{},[863,37515,27648],{"id":27647},[842,37517,37518],{},"For a production deployment, consider:",[871,37520,37521,37529],{},[874,37522,37523],{},[877,37524,37525,37527],{},[880,37526,27660],{},[880,37528,27663],{},[887,37530,37531,37540,37550,37560,37569],{},[877,37532,37533,37537],{},[892,37534,37535],{},[996,37536,18860],{},[892,37538,37539],{},"Add API keys or OAuth",[877,37541,37542,37547],{},[892,37543,37544],{},[996,37545,37546],{},"Rate limiting",[892,37548,37549],{},"Prevent abuse",[877,37551,37552,37557],{},[892,37553,37554],{},[996,37555,37556],{},"CORS",[892,37558,37559],{},"Restrict allowed origins",[877,37561,37562,37566],{},[892,37563,37564],{},[996,37565,27699],{},[892,37567,37568],{},"Already handled by Pydantic",[877,37570,37571,37576],{},[892,37572,37573],{},[996,37574,37575],{},"HTTPS",[892,37577,37578],{},"Enforced by Cloud Run",[4937,37580],{},[863,37582,31693],{"id":16446},[842,37584,37585],{},"This pipeline enables several advanced workflows:",[991,37587,37588,37594,37600,37606,37611],{},[961,37589,37590,37593],{},[996,37591,37592],{},"AI comparison"," - Compare manual annotations with AI-detected structures",[961,37595,37596,37599],{},[996,37597,37598],{},"Cross-DAW sync"," - Export from Ableton, import to REAPER/Logic",[961,37601,37602,37605],{},[996,37603,37604],{},"Version tracking"," - See how your arrangement evolved over time",[961,37607,37608,37610],{},[996,37609,35276],{}," - Share structure data with collaborators",[961,37612,37613,37616],{},[996,37614,37615],{},"Automated processing"," - Trigger downstream tasks on new exports",[4937,37618],{},[863,37620,27869],{"id":27868},[958,37622,37623,37628],{},[961,37624,37625,37627],{},[846,37626,27886],{"href":459}," - Build the Max for Live device",[961,37629,37630,37632],{},[846,37631,31743],{"href":101}," - AI-powered structure detection",[4937,37634],{},[863,37636,18681],{"id":18680},[842,37638,37639],{},"By connecting your Max for Live device to a cloud API, you transform Ableton from an isolated creative tool into a node in a larger data pipeline. Every arrangement decision, every locator you place, becomes structured data that can power visualizations, training datasets, and cross-platform workflows.",[842,37641,37642,37643,37646],{},"The key insight: ",[996,37644,37645],{},"your DAW is a data source",". Treating it that way opens up possibilities that weren't available when everything stayed local.",[1680,37648,37649],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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}",{"title":728,"searchDepth":729,"depth":729,"links":37651},[37652,37653,37654,37657,37661,37665,37666,37667,37668,37672,37677,37678,37682,37683,37684,37685],{"id":27939,"depth":729,"text":27940},{"id":35218,"depth":729,"text":35219},{"id":35284,"depth":729,"text":35285,"children":37655},[37656],{"id":35335,"depth":1112,"text":35336},{"id":35394,"depth":729,"text":35395,"children":37658},[37659,37660],{"id":35401,"depth":1112,"text":35402},{"id":35864,"depth":1112,"text":35865},{"id":35881,"depth":729,"text":35882,"children":37662},[37663,37664],{"id":35888,"depth":1112,"text":35889},{"id":35911,"depth":1112,"text":35912},{"id":36407,"depth":729,"text":36408},{"id":36440,"depth":729,"text":36441},{"id":36814,"depth":729,"text":36815},{"id":37175,"depth":729,"text":37176,"children":37669},[37670,37671],{"id":33369,"depth":1112,"text":34800},{"id":37256,"depth":1112,"text":37257},{"id":37277,"depth":729,"text":37278,"children":37673},[37674,37675,37676],{"id":37281,"depth":1112,"text":37282},{"id":37304,"depth":1112,"text":37305},{"id":37327,"depth":1112,"text":37328},{"id":37406,"depth":729,"text":1992},{"id":37458,"depth":729,"text":37459,"children":37679},[37680,37681],{"id":37462,"depth":1112,"text":37463},{"id":37489,"depth":1112,"text":37490},{"id":27647,"depth":729,"text":27648},{"id":16446,"depth":729,"text":31693},{"id":27868,"depth":729,"text":27869},{"id":18680,"depth":729,"text":18681},"2026-01-27T00:00:00.000Z","Send song structure data from Ableton Live to a cloud API. Build a complete DAW-to-database pipeline with real-time visualization.",{"src":37689},"/images/blog/musictechlab_blog_ableton-api-connection.webp",{"enabled":738,"items":37691},[37692,37694,37697,37699],{"text":37693,"icon":2895},"A cloud API turns local Ableton exports into a searchable, versioned database.",{"text":37695,"icon":37696},"FastAPI on Cloud Run receives, validates, and stores JSON from Max for Live.","i-lucide-rocket",{"text":37698,"icon":1067},"Every export gets a browser-based timeline visualization accessible from anywhere.",{"text":37700,"icon":1769},"The pipeline: Max for Live extracts metadata, sends to API, stores in PostgreSQL.",{},{"title":406,"description":37687},[26062,18784],"ve9KJaSJswdmeyVXGi-ApoLF6XbRdIXt3m0EvEJIAIE",{"id":37706,"title":458,"authors":37707,"badge":37710,"body":37711,"category":756,"client":723,"date":40838,"description":40839,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":40840,"keyTakeaways":40842,"meta":40853,"navigation":738,"path":459,"seo":40854,"status":723,"stem":460,"tags":40855,"teaser":723,"__hash__":40856},"posts/blog/software-development/exporting-ableton-live-locators-to-json-with-max-for-live.md",[37708],{"name":834,"to":720,"avatar":37709},{"src":722},{"label":35180,"color":32438},{"type":725,"value":37712,"toc":40807},[37713,37716,37723,37726,37740,37743,37747,37750,37976,37979,37993,37995,37998,38041,38044,38047,38053,38073,38077,38095,38099,38102,38119,38123,38132,38172,38187,38191,38194,38222,38226,38229,39133,39137,39143,39149,39166,39174,39183,39187,39190,39202,39206,39210,39213,39218,39242,39246,39249,39254,39297,39301,39304,39311,39343,39347,39359,39362,39373,39378,39408,39412,39415,39612,39621,39632,39636,39640,39643,39649,39652,39666,39670,39682,39692,39696,39707,39711,39723,39754,39758,39765,39789,39791,39794,39857,39861,39864,40789,40791,40794,40801,40804],[5539,37714,27886],{"id":37715},"exporting-ableton-live-locators-to-json-with-max-for-live",[842,37717,37718,37719,37722],{},"When building music production tools and AI-powered analysis pipelines, one of the first challenges is getting structured data out of your DAW. Today, we'll walk through creating a ",[996,37720,37721],{},"Max for Live device"," that exports arrangement locators from Ableton Live to a clean JSON format.",[842,37724,37725],{},"This article documents a real proof-of-concept we built - a simple \"EXPORT JSON\" button that extracts:",[958,37727,37728,37734],{},[961,37729,37730,37733],{},[996,37731,37732],{},"BPM"," (tempo)",[961,37735,37736,37739],{},[996,37737,37738],{},"Locators/sections"," (INTRO, VERSE, CHORUS, etc.) with timestamps in seconds",[842,37741,37742],{},"The result is a portable JSON file that can feed into visualization tools, AI models, or cross-DAW workflows.",[863,37744,37746],{"id":37745},"the-goal","The Goal",[842,37748,37749],{},"We want a one-click solution inside Ableton Live that produces output like this:",[1013,37751,37753],{"className":1136,"code":37752,"language":1139,"meta":728,"style":728},"{\n  \"project\": \"my_track\",\n  \"bpm\": 103.34,\n  \"sections\": [\n    { \"label\": \"INTRO\", \"time_seconds\": 0 },\n    { \"label\": \"VERSE\", \"time_seconds\": 16 },\n    { \"label\": \"BRIDGE\", \"time_seconds\": 80 },\n    { \"label\": \"CHORUS\", \"time_seconds\": 112 },\n    { \"label\": \"OUTRO\", \"time_seconds\": 224 }\n  ]\n}\n",[895,37754,37755,37759,37777,37791,37803,37835,37867,37901,37934,37968,37972],{"__ignoreMap":728},[1086,37756,37757],{"class":1088,"line":1089},[1086,37758,1147],{"class":1146},[1086,37760,37761,37763,37765,37767,37769,37771,37773,37775],{"class":1088,"line":729},[1086,37762,1152],{"class":1146},[1086,37764,35426],{"class":1155},[1086,37766,1159],{"class":1146},[1086,37768,1133],{"class":1146},[1086,37770,1195],{"class":1146},[1086,37772,35435],{"class":1096},[1086,37774,1159],{"class":1146},[1086,37776,1202],{"class":1146},[1086,37778,37779,37781,37783,37785,37787,37789],{"class":1088,"line":1112},[1086,37780,1152],{"class":1146},[1086,37782,31015],{"class":1155},[1086,37784,1159],{"class":1146},[1086,37786,1133],{"class":1146},[1086,37788,35452],{"class":1187},[1086,37790,1202],{"class":1146},[1086,37792,37793,37795,37797,37799,37801],{"class":1088,"line":1181},[1086,37794,1152],{"class":1146},[1086,37796,30324],{"class":1155},[1086,37798,1159],{"class":1146},[1086,37800,1133],{"class":1146},[1086,37802,6580],{"class":1146},[1086,37804,37805,37807,37809,37811,37813,37815,37817,37819,37821,37823,37825,37827,37829,37831,37833],{"class":1088,"line":1205},[1086,37806,30335],{"class":1146},[1086,37808,1195],{"class":1146},[1086,37810,10840],{"class":1092},[1086,37812,1159],{"class":1146},[1086,37814,1133],{"class":1146},[1086,37816,1195],{"class":1146},[1086,37818,35544],{"class":1096},[1086,37820,1159],{"class":1146},[1086,37822,1227],{"class":1146},[1086,37824,1195],{"class":1146},[1086,37826,35553],{"class":1092},[1086,37828,1159],{"class":1146},[1086,37830,1133],{"class":1146},[1086,37832,22718],{"class":1187},[1086,37834,4792],{"class":1146},[1086,37836,37837,37839,37841,37843,37845,37847,37849,37851,37853,37855,37857,37859,37861,37863,37865],{"class":1088,"line":1276},[1086,37838,30335],{"class":1146},[1086,37840,1195],{"class":1146},[1086,37842,10840],{"class":1092},[1086,37844,1159],{"class":1146},[1086,37846,1133],{"class":1146},[1086,37848,1195],{"class":1146},[1086,37850,35578],{"class":1096},[1086,37852,1159],{"class":1146},[1086,37854,1227],{"class":1146},[1086,37856,1195],{"class":1146},[1086,37858,35553],{"class":1092},[1086,37860,1159],{"class":1146},[1086,37862,1133],{"class":1146},[1086,37864,35593],{"class":1187},[1086,37866,4792],{"class":1146},[1086,37868,37869,37871,37873,37875,37877,37879,37881,37884,37886,37888,37890,37892,37894,37896,37899],{"class":1088,"line":1282},[1086,37870,30335],{"class":1146},[1086,37872,1195],{"class":1146},[1086,37874,10840],{"class":1092},[1086,37876,1159],{"class":1146},[1086,37878,1133],{"class":1146},[1086,37880,1195],{"class":1146},[1086,37882,37883],{"class":1096},"BRIDGE",[1086,37885,1159],{"class":1146},[1086,37887,1227],{"class":1146},[1086,37889,1195],{"class":1146},[1086,37891,35553],{"class":1092},[1086,37893,1159],{"class":1146},[1086,37895,1133],{"class":1146},[1086,37897,37898],{"class":1187}," 80",[1086,37900,4792],{"class":1146},[1086,37902,37903,37905,37907,37909,37911,37913,37915,37917,37919,37921,37923,37925,37927,37929,37932],{"class":1088,"line":1288},[1086,37904,30335],{"class":1146},[1086,37906,1195],{"class":1146},[1086,37908,10840],{"class":1092},[1086,37910,1159],{"class":1146},[1086,37912,1133],{"class":1146},[1086,37914,1195],{"class":1146},[1086,37916,35612],{"class":1096},[1086,37918,1159],{"class":1146},[1086,37920,1227],{"class":1146},[1086,37922,1195],{"class":1146},[1086,37924,35553],{"class":1092},[1086,37926,1159],{"class":1146},[1086,37928,1133],{"class":1146},[1086,37930,37931],{"class":1187}," 112",[1086,37933,4792],{"class":1146},[1086,37935,37936,37938,37940,37942,37944,37946,37948,37951,37953,37955,37957,37959,37961,37963,37966],{"class":1088,"line":2685},[1086,37937,30335],{"class":1146},[1086,37939,1195],{"class":1146},[1086,37941,10840],{"class":1092},[1086,37943,1159],{"class":1146},[1086,37945,1133],{"class":1146},[1086,37947,1195],{"class":1146},[1086,37949,37950],{"class":1096},"OUTRO",[1086,37952,1159],{"class":1146},[1086,37954,1227],{"class":1146},[1086,37956,1195],{"class":1146},[1086,37958,35553],{"class":1092},[1086,37960,1159],{"class":1146},[1086,37962,1133],{"class":1146},[1086,37964,37965],{"class":1187}," 224",[1086,37967,27132],{"class":1146},[1086,37969,37970],{"class":1088,"line":2700},[1086,37971,9465],{"class":1146},[1086,37973,37974],{"class":1088,"line":3398},[1086,37975,1291],{"class":1146},[842,37977,37978],{},"This JSON can then be:",[958,37980,37981,37984,37987,37990],{},[961,37982,37983],{},"Imported into other DAWs (REAPER, Logic, etc.)",[961,37985,37986],{},"Used for automated video editing synced to song structure",[961,37988,37989],{},"Fed into machine learning models for training",[961,37991,37992],{},"Displayed in custom visualization tools",[863,37994,15348],{"id":15347},[842,37996,37997],{},"To follow along, you'll need:",[871,37999,38000,38009],{},[874,38001,38002],{},[877,38003,38004,38007],{},[880,38005,38006],{},"Requirement",[880,38008,2719],{},[887,38010,38011,38021,38031],{},[877,38012,38013,38018],{},[892,38014,38015],{},[996,38016,38017],{},"Ableton Live 12 Suite",[892,38019,38020],{},"Max for Live is included in Suite",[877,38022,38023,38028],{},[892,38024,38025],{},[996,38026,38027],{},"Basic Max/MSP knowledge",[892,38029,38030],{},"We'll keep it simple",[877,38032,38033,38038],{},[892,38034,38035],{},[996,38036,38037],{},"Text editor",[892,38039,38040],{},"For the JavaScript file",[863,38042,38043],{"id":18899},"Architecture Overview",[842,38045,38046],{},"Our device consists of three components:",[1013,38048,38051],{"className":38049,"code":38050,"language":1018},[1016],"┌─────────────────┐     ┌──────────────────────┐     ┌────────────────┐\n│   textbutton    │────▶│  js marker_export.js │────▶│   textedit     │\n│  \"EXPORT JSON\"  │     │   (LiveAPI magic)    │     │  (JSON preview)│\n└─────────────────┘     └──────────────────────┘     └────────────────┘\n",[895,38052,38050],{"__ignoreMap":728},[991,38054,38055,38061,38067],{},[961,38056,38057,38060],{},[996,38058,38059],{},"textbutton"," - The user clicks this to trigger export",[961,38062,38063,38066],{},[996,38064,38065],{},"js marker_export.js"," - JavaScript that queries Ableton's Live Object Model",[961,38068,38069,38072],{},[996,38070,38071],{},"textedit"," - Read-only display showing the exported JSON",[863,38074,38076],{"id":38075},"building-the-max-for-live-device","Building the Max for Live Device",[1045,38078,31345,38080],{"style":38079},"display: flex; justify-content: center; margin: 2rem 0;",[38081,38082,38084,38085,38084,38090,31345],"figure",{"style":38083},"margin: 0; text-align: center;","\n    ",[1027,38086],{"src":38087,"alt":38088,"style":38089},"/images/blog/musictechlab_blog_ableton-locators-json-max-patcher.webp","Max for Live patcher with JavaScript code","max-width: 600px; width: 100%; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: block;",[38091,38092,38094],"figcaption",{"style":38093},"margin-top: 0.5rem; color: #666; font-size: 0.875rem;","Max for Live patcher with the JavaScript code editor open",[1074,38096,38098],{"id":38097},"step-1-create-the-patcher","Step 1: Create the Patcher",[842,38100,38101],{},"Open Max for Live in Ableton (create a new Max MIDI Effect), then add these objects:",[991,38103,38104,38109,38114],{},[961,38105,38106,38108],{},[996,38107,38059],{}," - Set the text to \"EXPORT JSON\"",[961,38110,38111,38113],{},[996,38112,38065],{}," - This will hold our JavaScript logic",[961,38115,38116,38118],{},[996,38117,38071],{}," - Enable \"Read Only\" in the Inspector",[1074,38120,38122],{"id":38121},"step-2-wire-the-connections","Step 2: Wire the Connections",[842,38124,38125,38126,38128,38129,1133],{},"This is where things get tricky. The ",[895,38127,38059],{}," object has ",[996,38130,38131],{},"three outlets",[871,38133,38134,38144],{},[874,38135,38136],{},[877,38137,38138,38141],{},[880,38139,38140],{},"Outlet",[880,38142,38143],{},"Output",[887,38145,38146,38154,38162],{},[877,38147,38148,38151],{},[892,38149,38150],{},"1st (left)",[892,38152,38153],{},"Button text",[877,38155,38156,38159],{},[892,38157,38158],{},"2nd (middle)",[892,38160,38161],{},"Mouse events",[877,38163,38164,38169],{},[892,38165,38166],{},[996,38167,38168],{},"3rd (right)",[892,38170,38171],{},"Integer (1 on click)",[842,38173,38174,38175,38178,38179,38181,38182,38184,38185,861],{},"Connect the ",[996,38176,38177],{},"third outlet"," of ",[895,38180,38059],{}," to the inlet of ",[895,38183,38065],{},", then connect the outlet of the JS object to ",[895,38186,38071],{},[1074,38188,38190],{"id":38189},"step-3-set-up-presentation-mode","Step 3: Set Up Presentation Mode",[842,38192,38193],{},"For the device to be usable in Live's UI:",[991,38195,38196,38203,38206,38212,38219],{},[961,38197,38198,38199,1589,38201],{},"Select both ",[895,38200,38059],{},[895,38202,38071],{},[961,38204,38205],{},"Open the Inspector (right sidebar)",[961,38207,38208,38209],{},"Enable ",[996,38210,38211],{},"\"Include in Presentation\"",[961,38213,38214,38215,38218],{},"Switch to ",[996,38216,38217],{},"Presentation Mode"," (View → Presentation Mode)",[961,38220,38221],{},"Arrange the objects nicely",[863,38223,38225],{"id":38224},"the-javascript-marker_exportjs","The JavaScript: marker_export.js",[842,38227,38228],{},"Here's the complete script that powers our device:",[1013,38230,38232],{"className":33433,"code":38231,"language":33435,"meta":728,"style":728},"autowatch = 1;\noutlets = 1;\n\nfunction msg_int(v) {\n  if (v === 1) bang();\n}\n\nfunction bang() {\n  try {\n    var song = new LiveAPI(\"live_set\");\n\n    // --- PROJECT NAME ---\n    var name = song.get(\"name\");\n    name = Array.isArray(name) ? name[0] : name;\n    if (!name || name === \"\") name = \"untitled\";\n    name = name.replace(/[^a-z0-9_\\-]/gi, \"_\");\n\n    // --- BPM ---\n    var bpm = song.get(\"tempo\");\n    bpm = Array.isArray(bpm) ? bpm[0] : bpm;\n\n    // --- LOCATORS ---\n    var locatorIds = song.get(\"cue_points\");\n    var sections = [];\n\n    if (Array.isArray(locatorIds)) {\n      for (var i = 0; i \u003C locatorIds.length; i++) {\n        if (typeof locatorIds[i] !== \"number\") continue;\n\n        var l = new LiveAPI(\"id \" + locatorIds[i]);\n        var label = l.get(\"name\");\n        var time = l.get(\"time\");\n\n        sections.push({\n          label: Array.isArray(label) ? label[0] : label,\n          time_seconds: Array.isArray(time) ? time[0] : time\n        });\n      }\n    }\n\n    // --- BUILD JSON ---\n    var data = {\n      project: name,\n      bpm: bpm,\n      sections: sections\n    };\n\n    var json = JSON.stringify(data, null, 2);\n\n    // Send to UI (textedit)\n    outlet(0, \"set\", json);\n\n    // Log to Max Console\n    post(\"EXPORT SUCCESS\\\\n\");\n\n  } catch (e) {\n    post(\"EXPORT ERROR:\", e.toString(), \"\\\\n\");\n  }\n}\n",[895,38233,38234,38245,38256,38260,38276,38297,38301,38305,38315,38322,38347,38351,38356,38382,38420,38455,38497,38501,38506,38532,38566,38570,38575,38602,38615,38619,38642,38683,38716,38720,38754,38781,38808,38812,38824,38859,38893,38902,38906,38910,38914,38919,38929,38940,38951,38961,38966,38970,38999,39003,39008,39033,39037,39042,39066,39070,39086,39125,39129],{"__ignoreMap":728},[1086,38235,38236,38239,38241,38243],{"class":1088,"line":1089},[1086,38237,38238],{"class":1436},"autowatch ",[1086,38240,1440],{"class":1146},[1086,38242,23488],{"class":1187},[1086,38244,33466],{"class":1146},[1086,38246,38247,38250,38252,38254],{"class":1088,"line":729},[1086,38248,38249],{"class":1436},"outlets ",[1086,38251,1440],{"class":1146},[1086,38253,23488],{"class":1187},[1086,38255,33466],{"class":1146},[1086,38257,38258],{"class":1088,"line":1112},[1086,38259,3390],{"emptyLinePlaceholder":738},[1086,38261,38262,38264,38267,38269,38272,38274],{"class":1088,"line":1181},[1086,38263,35934],{"class":1155},[1086,38265,38266],{"class":1105}," msg_int",[1086,38268,1398],{"class":1146},[1086,38270,38271],{"class":1401},"v",[1086,38273,1410],{"class":1146},[1086,38275,1164],{"class":1146},[1086,38277,38278,38280,38282,38284,38286,38288,38290,38293,38295],{"class":1088,"line":1205},[1086,38279,27233],{"class":1423},[1086,38281,5979],{"class":4109},[1086,38283,38271],{"class":1436},[1086,38285,27244],{"class":1146},[1086,38287,23488],{"class":1187},[1086,38289,2778],{"class":4109},[1086,38291,38292],{"class":1105},"bang",[1086,38294,2516],{"class":4109},[1086,38296,33466],{"class":1146},[1086,38298,38299],{"class":1088,"line":1276},[1086,38300,1291],{"class":1146},[1086,38302,38303],{"class":1088,"line":1282},[1086,38304,3390],{"emptyLinePlaceholder":738},[1086,38306,38307,38309,38311,38313],{"class":1088,"line":1288},[1086,38308,35934],{"class":1155},[1086,38310,35937],{"class":1105},[1086,38312,2516],{"class":1146},[1086,38314,1164],{"class":1146},[1086,38316,38317,38320],{"class":1088,"line":2685},[1086,38318,38319],{"class":1423},"  try",[1086,38321,1164],{"class":1146},[1086,38323,38324,38327,38329,38331,38333,38335,38337,38339,38341,38343,38345],{"class":1088,"line":2700},[1086,38325,38326],{"class":1155},"    var",[1086,38328,35983],{"class":1436},[1086,38330,19552],{"class":1146},[1086,38332,26591],{"class":1146},[1086,38334,35958],{"class":1105},[1086,38336,1398],{"class":4109},[1086,38338,1159],{"class":1146},[1086,38340,35965],{"class":1096},[1086,38342,1159],{"class":1146},[1086,38344,1410],{"class":4109},[1086,38346,33466],{"class":1146},[1086,38348,38349],{"class":1088,"line":3398},[1086,38350,3390],{"emptyLinePlaceholder":738},[1086,38352,38353],{"class":1088,"line":1715},[1086,38354,38355],{"class":1427},"    // --- PROJECT NAME ---\n",[1086,38357,38358,38360,38362,38364,38366,38368,38370,38372,38374,38376,38378,38380],{"class":1088,"line":3409},[1086,38359,38326],{"class":1155},[1086,38361,24696],{"class":1436},[1086,38363,19552],{"class":1146},[1086,38365,35983],{"class":1436},[1086,38367,861],{"class":1146},[1086,38369,10812],{"class":1105},[1086,38371,1398],{"class":4109},[1086,38373,1159],{"class":1146},[1086,38375,4184],{"class":1096},[1086,38377,1159],{"class":1146},[1086,38379,1410],{"class":4109},[1086,38381,33466],{"class":1146},[1086,38383,38384,38387,38389,38392,38394,38397,38399,38401,38403,38406,38408,38410,38412,38414,38416,38418],{"class":1088,"line":3415},[1086,38385,38386],{"class":1436},"    name",[1086,38388,19552],{"class":1146},[1086,38390,38391],{"class":1436}," Array",[1086,38393,861],{"class":1146},[1086,38395,38396],{"class":1105},"isArray",[1086,38398,1398],{"class":4109},[1086,38400,4184],{"class":1436},[1086,38402,2778],{"class":4109},[1086,38404,38405],{"class":1146},"?",[1086,38407,24696],{"class":1436},[1086,38409,4340],{"class":4109},[1086,38411,4417],{"class":1187},[1086,38413,33549],{"class":4109},[1086,38415,1133],{"class":1146},[1086,38417,24696],{"class":1436},[1086,38419,33466],{"class":1146},[1086,38421,38422,38424,38426,38429,38431,38434,38436,38438,38440,38442,38444,38446,38448,38451,38453],{"class":1088,"line":3421},[1086,38423,6474],{"class":1423},[1086,38425,5979],{"class":4109},[1086,38427,38428],{"class":1146},"!",[1086,38430,4184],{"class":1436},[1086,38432,38433],{"class":1146}," ||",[1086,38435,24696],{"class":1436},[1086,38437,27244],{"class":1146},[1086,38439,19571],{"class":1146},[1086,38441,2778],{"class":4109},[1086,38443,4184],{"class":1436},[1086,38445,19552],{"class":1146},[1086,38447,1195],{"class":1146},[1086,38449,38450],{"class":1096},"untitled",[1086,38452,1159],{"class":1146},[1086,38454,33466],{"class":1146},[1086,38456,38457,38459,38461,38463,38465,38468,38470,38473,38476,38479,38482,38485,38487,38489,38491,38493,38495],{"class":1088,"line":3427},[1086,38458,38386],{"class":1436},[1086,38460,19552],{"class":1146},[1086,38462,24696],{"class":1436},[1086,38464,861],{"class":1146},[1086,38466,38467],{"class":1105},"replace",[1086,38469,1398],{"class":4109},[1086,38471,38472],{"class":1146},"/[^",[1086,38474,38475],{"class":1096},"a-z0-9_",[1086,38477,38478],{"class":1436},"\\-",[1086,38480,38481],{"class":1146},"]/",[1086,38483,38484],{"class":1187},"gi",[1086,38486,1227],{"class":1146},[1086,38488,1195],{"class":1146},[1086,38490,25919],{"class":1096},[1086,38492,1159],{"class":1146},[1086,38494,1410],{"class":4109},[1086,38496,33466],{"class":1146},[1086,38498,38499],{"class":1088,"line":3433},[1086,38500,3390],{"emptyLinePlaceholder":738},[1086,38502,38503],{"class":1088,"line":3439},[1086,38504,38505],{"class":1427},"    // --- BPM ---\n",[1086,38507,38508,38510,38512,38514,38516,38518,38520,38522,38524,38526,38528,38530],{"class":1088,"line":3444},[1086,38509,38326],{"class":1155},[1086,38511,36275],{"class":1436},[1086,38513,19552],{"class":1146},[1086,38515,35983],{"class":1436},[1086,38517,861],{"class":1146},[1086,38519,10812],{"class":1105},[1086,38521,1398],{"class":4109},[1086,38523,1159],{"class":1146},[1086,38525,36021],{"class":1096},[1086,38527,1159],{"class":1146},[1086,38529,1410],{"class":4109},[1086,38531,33466],{"class":1146},[1086,38533,38534,38536,38538,38540,38542,38544,38546,38548,38550,38552,38554,38556,38558,38560,38562,38564],{"class":1088,"line":3450},[1086,38535,36270],{"class":1436},[1086,38537,19552],{"class":1146},[1086,38539,38391],{"class":1436},[1086,38541,861],{"class":1146},[1086,38543,38396],{"class":1105},[1086,38545,1398],{"class":4109},[1086,38547,31015],{"class":1436},[1086,38549,2778],{"class":4109},[1086,38551,38405],{"class":1146},[1086,38553,36275],{"class":1436},[1086,38555,4340],{"class":4109},[1086,38557,4417],{"class":1187},[1086,38559,33549],{"class":4109},[1086,38561,1133],{"class":1146},[1086,38563,36275],{"class":1436},[1086,38565,33466],{"class":1146},[1086,38567,38568],{"class":1088,"line":3456},[1086,38569,3390],{"emptyLinePlaceholder":738},[1086,38571,38572],{"class":1088,"line":3462},[1086,38573,38574],{"class":1427},"    // --- LOCATORS ---\n",[1086,38576,38577,38579,38582,38584,38586,38588,38590,38592,38594,38596,38598,38600],{"class":1088,"line":3467},[1086,38578,38326],{"class":1155},[1086,38580,38581],{"class":1436}," locatorIds",[1086,38583,19552],{"class":1146},[1086,38585,35983],{"class":1436},[1086,38587,861],{"class":1146},[1086,38589,10812],{"class":1105},[1086,38591,1398],{"class":4109},[1086,38593,1159],{"class":1146},[1086,38595,36105],{"class":1096},[1086,38597,1159],{"class":1146},[1086,38599,1410],{"class":4109},[1086,38601,33466],{"class":1146},[1086,38603,38604,38606,38608,38610,38613],{"class":1088,"line":3473},[1086,38605,38326],{"class":1155},[1086,38607,36315],{"class":1436},[1086,38609,19552],{"class":1146},[1086,38611,38612],{"class":4109}," []",[1086,38614,33466],{"class":1146},[1086,38616,38617],{"class":1088,"line":3479},[1086,38618,3390],{"emptyLinePlaceholder":738},[1086,38620,38621,38623,38625,38628,38630,38632,38634,38637,38640],{"class":1088,"line":3485},[1086,38622,6474],{"class":1423},[1086,38624,5979],{"class":4109},[1086,38626,38627],{"class":1436},"Array",[1086,38629,861],{"class":1146},[1086,38631,38396],{"class":1105},[1086,38633,1398],{"class":4109},[1086,38635,38636],{"class":1436},"locatorIds",[1086,38638,38639],{"class":4109},")) ",[1086,38641,1147],{"class":1146},[1086,38643,38644,38647,38649,38652,38654,38656,38658,38660,38662,38665,38667,38669,38672,38674,38676,38679,38681],{"class":1088,"line":3491},[1086,38645,38646],{"class":1423},"      for",[1086,38648,5979],{"class":4109},[1086,38650,38651],{"class":1155},"var",[1086,38653,33757],{"class":1436},[1086,38655,19552],{"class":1146},[1086,38657,22718],{"class":1187},[1086,38659,4639],{"class":1146},[1086,38661,33757],{"class":1436},[1086,38663,38664],{"class":1146}," \u003C",[1086,38666,38581],{"class":1436},[1086,38668,861],{"class":1146},[1086,38670,38671],{"class":1436},"length",[1086,38673,4639],{"class":1146},[1086,38675,33757],{"class":1436},[1086,38677,38678],{"class":1146},"++",[1086,38680,2778],{"class":4109},[1086,38682,1147],{"class":1146},[1086,38684,38685,38687,38689,38692,38694,38696,38698,38700,38703,38705,38707,38709,38711,38714],{"class":1088,"line":3497},[1086,38686,6800],{"class":1423},[1086,38688,5979],{"class":4109},[1086,38690,38691],{"class":1146},"typeof",[1086,38693,38581],{"class":1436},[1086,38695,4340],{"class":4109},[1086,38697,33784],{"class":1436},[1086,38699,33549],{"class":4109},[1086,38701,38702],{"class":1146},"!==",[1086,38704,1195],{"class":1146},[1086,38706,18485],{"class":1096},[1086,38708,1159],{"class":1146},[1086,38710,2778],{"class":4109},[1086,38712,38713],{"class":1423},"continue",[1086,38715,33466],{"class":1146},[1086,38717,38718],{"class":1088,"line":3503},[1086,38719,3390],{"emptyLinePlaceholder":738},[1086,38721,38722,38725,38728,38730,38732,38734,38736,38738,38740,38742,38744,38746,38748,38750,38752],{"class":1088,"line":3509},[1086,38723,38724],{"class":1155},"        var",[1086,38726,38727],{"class":1436}," l",[1086,38729,19552],{"class":1146},[1086,38731,26591],{"class":1146},[1086,38733,35958],{"class":1105},[1086,38735,1398],{"class":4109},[1086,38737,1159],{"class":1146},[1086,38739,36153],{"class":1096},[1086,38741,1159],{"class":1146},[1086,38743,33787],{"class":1146},[1086,38745,38581],{"class":1436},[1086,38747,4340],{"class":4109},[1086,38749,33784],{"class":1436},[1086,38751,20587],{"class":4109},[1086,38753,33466],{"class":1146},[1086,38755,38756,38758,38761,38763,38765,38767,38769,38771,38773,38775,38777,38779],{"class":1088,"line":3515},[1086,38757,38724],{"class":1155},[1086,38759,38760],{"class":1436}," label",[1086,38762,19552],{"class":1146},[1086,38764,38727],{"class":1436},[1086,38766,861],{"class":1146},[1086,38768,10812],{"class":1105},[1086,38770,1398],{"class":4109},[1086,38772,1159],{"class":1146},[1086,38774,4184],{"class":1096},[1086,38776,1159],{"class":1146},[1086,38778,1410],{"class":4109},[1086,38780,33466],{"class":1146},[1086,38782,38783,38785,38788,38790,38792,38794,38796,38798,38800,38802,38804,38806],{"class":1088,"line":3520},[1086,38784,38724],{"class":1155},[1086,38786,38787],{"class":1436}," time",[1086,38789,19552],{"class":1146},[1086,38791,38727],{"class":1436},[1086,38793,861],{"class":1146},[1086,38795,10812],{"class":1105},[1086,38797,1398],{"class":4109},[1086,38799,1159],{"class":1146},[1086,38801,31109],{"class":1096},[1086,38803,1159],{"class":1146},[1086,38805,1410],{"class":4109},[1086,38807,33466],{"class":1146},[1086,38809,38810],{"class":1088,"line":3526},[1086,38811,3390],{"emptyLinePlaceholder":738},[1086,38813,38814,38816,38818,38820,38822],{"class":1088,"line":3531},[1086,38815,37058],{"class":1436},[1086,38817,861],{"class":1146},[1086,38819,36171],{"class":1105},[1086,38821,1398],{"class":4109},[1086,38823,1147],{"class":1146},[1086,38825,38826,38829,38831,38833,38835,38837,38839,38841,38843,38845,38847,38849,38851,38853,38855,38857],{"class":1088,"line":3537},[1086,38827,38828],{"class":4109},"          label",[1086,38830,1133],{"class":1146},[1086,38832,38391],{"class":1436},[1086,38834,861],{"class":1146},[1086,38836,38396],{"class":1105},[1086,38838,1398],{"class":4109},[1086,38840,10840],{"class":1436},[1086,38842,2778],{"class":4109},[1086,38844,38405],{"class":1146},[1086,38846,38760],{"class":1436},[1086,38848,4340],{"class":4109},[1086,38850,4417],{"class":1187},[1086,38852,33549],{"class":4109},[1086,38854,1133],{"class":1146},[1086,38856,38760],{"class":1436},[1086,38858,1202],{"class":1146},[1086,38860,38861,38864,38866,38868,38870,38872,38874,38876,38878,38880,38882,38884,38886,38888,38890],{"class":1088,"line":3543},[1086,38862,38863],{"class":4109},"          time_seconds",[1086,38865,1133],{"class":1146},[1086,38867,38391],{"class":1436},[1086,38869,861],{"class":1146},[1086,38871,38396],{"class":1105},[1086,38873,1398],{"class":4109},[1086,38875,31109],{"class":1436},[1086,38877,2778],{"class":4109},[1086,38879,38405],{"class":1146},[1086,38881,38787],{"class":1436},[1086,38883,4340],{"class":4109},[1086,38885,4417],{"class":1187},[1086,38887,33549],{"class":4109},[1086,38889,1133],{"class":1146},[1086,38891,38892],{"class":1436}," time\n",[1086,38894,38895,38898,38900],{"class":1088,"line":3549},[1086,38896,38897],{"class":1146},"        }",[1086,38899,1410],{"class":4109},[1086,38901,33466],{"class":1146},[1086,38903,38904],{"class":1088,"line":3555},[1086,38905,26783],{"class":1146},[1086,38907,38908],{"class":1088,"line":3561},[1086,38909,1279],{"class":1146},[1086,38911,38912],{"class":1088,"line":3567},[1086,38913,3390],{"emptyLinePlaceholder":738},[1086,38915,38916],{"class":1088,"line":17749},[1086,38917,38918],{"class":1427},"    // --- BUILD JSON ---\n",[1086,38920,38921,38923,38925,38927],{"class":1088,"line":19843},[1086,38922,38326],{"class":1155},[1086,38924,20125],{"class":1436},[1086,38926,19552],{"class":1146},[1086,38928,1164],{"class":1146},[1086,38930,38931,38934,38936,38938],{"class":1088,"line":19848},[1086,38932,38933],{"class":4109},"      project",[1086,38935,1133],{"class":1146},[1086,38937,24696],{"class":1436},[1086,38939,1202],{"class":1146},[1086,38941,38942,38945,38947,38949],{"class":1088,"line":19880},[1086,38943,38944],{"class":4109},"      bpm",[1086,38946,1133],{"class":1146},[1086,38948,36275],{"class":1436},[1086,38950,1202],{"class":1146},[1086,38952,38953,38956,38958],{"class":1088,"line":19896},[1086,38954,38955],{"class":4109},"      sections",[1086,38957,1133],{"class":1146},[1086,38959,38960],{"class":1436}," sections\n",[1086,38962,38963],{"class":1088,"line":19919},[1086,38964,38965],{"class":1146},"    };\n",[1086,38967,38968],{"class":1088,"line":19927},[1086,38969,3390],{"emptyLinePlaceholder":738},[1086,38971,38972,38974,38976,38978,38980,38982,38984,38986,38988,38990,38993,38995,38997],{"class":1088,"line":19948},[1086,38973,38326],{"class":1155},[1086,38975,1463],{"class":1436},[1086,38977,19552],{"class":1146},[1086,38979,26986],{"class":1436},[1086,38981,861],{"class":1146},[1086,38983,26991],{"class":1105},[1086,38985,1398],{"class":4109},[1086,38987,4337],{"class":1436},[1086,38989,1227],{"class":1146},[1086,38991,38992],{"class":1146}," null,",[1086,38994,9048],{"class":1187},[1086,38996,1410],{"class":4109},[1086,38998,33466],{"class":1146},[1086,39000,39001],{"class":1088,"line":19968},[1086,39002,3390],{"emptyLinePlaceholder":738},[1086,39004,39005],{"class":1088,"line":19987},[1086,39006,39007],{"class":1427},"    // Send to UI (textedit)\n",[1086,39009,39010,39013,39015,39017,39019,39021,39023,39025,39027,39029,39031],{"class":1088,"line":20007},[1086,39011,39012],{"class":1105},"    outlet",[1086,39014,1398],{"class":4109},[1086,39016,4417],{"class":1187},[1086,39018,1227],{"class":1146},[1086,39020,1195],{"class":1146},[1086,39022,20205],{"class":1096},[1086,39024,1159],{"class":1146},[1086,39026,1227],{"class":1146},[1086,39028,1463],{"class":1436},[1086,39030,1410],{"class":4109},[1086,39032,33466],{"class":1146},[1086,39034,39035],{"class":1088,"line":20013},[1086,39036,3390],{"emptyLinePlaceholder":738},[1086,39038,39039],{"class":1088,"line":20021},[1086,39040,39041],{"class":1427},"    // Log to Max Console\n",[1086,39043,39044,39047,39049,39051,39054,39057,39060,39062,39064],{"class":1088,"line":20051},[1086,39045,39046],{"class":1105},"    post",[1086,39048,1398],{"class":4109},[1086,39050,1159],{"class":1146},[1086,39052,39053],{"class":1096},"EXPORT SUCCESS",[1086,39055,39056],{"class":1436},"\\\\",[1086,39058,39059],{"class":1096},"n",[1086,39061,1159],{"class":1146},[1086,39063,1410],{"class":4109},[1086,39065,33466],{"class":1146},[1086,39067,39068],{"class":1088,"line":20072},[1086,39069,3390],{"emptyLinePlaceholder":738},[1086,39071,39072,39074,39077,39079,39082,39084],{"class":1088,"line":20077},[1086,39073,4797],{"class":1146},[1086,39075,39076],{"class":1423}," catch",[1086,39078,5979],{"class":4109},[1086,39080,39081],{"class":1436},"e",[1086,39083,2778],{"class":4109},[1086,39085,1147],{"class":1146},[1086,39087,39088,39090,39092,39094,39097,39099,39101,39104,39106,39109,39111,39113,39115,39117,39119,39121,39123],{"class":1088,"line":20083},[1086,39089,39046],{"class":1105},[1086,39091,1398],{"class":4109},[1086,39093,1159],{"class":1146},[1086,39095,39096],{"class":1096},"EXPORT ERROR:",[1086,39098,1159],{"class":1146},[1086,39100,1227],{"class":1146},[1086,39102,39103],{"class":1436}," e",[1086,39105,861],{"class":1146},[1086,39107,39108],{"class":1105},"toString",[1086,39110,2516],{"class":4109},[1086,39112,1227],{"class":1146},[1086,39114,1195],{"class":1146},[1086,39116,39056],{"class":1436},[1086,39118,39059],{"class":1096},[1086,39120,1159],{"class":1146},[1086,39122,1410],{"class":4109},[1086,39124,33466],{"class":1146},[1086,39126,39127],{"class":1088,"line":20095},[1086,39128,1285],{"class":1146},[1086,39130,39131],{"class":1088,"line":20112},[1086,39132,1291],{"class":1146},[1074,39134,39136],{"id":39135},"key-concepts-explained","Key Concepts Explained",[842,39138,39139,39142],{},[996,39140,39141],{},"autowatch = 1"," - Tells Max to reload the script automatically when the file changes. Essential during development.",[842,39144,39145,39148],{},[996,39146,39147],{},"LiveAPI"," - The bridge between JavaScript and Ableton's Live Object Model. We use it to access:",[958,39150,39151,39156,39161],{},[961,39152,39153,39155],{},[895,39154,35965],{}," - The current project",[961,39157,39158,39160],{},[895,39159,36021],{}," - Current BPM",[961,39162,39163,39165],{},[895,39164,36105],{}," - Array of locator IDs",[842,39167,39168,39171,39172,861],{},[996,39169,39170],{},"Array handling"," - LiveAPI often returns single values wrapped in arrays, so we consistently check with ",[895,39173,36401],{},[842,39175,39176,39179,39180,39182],{},[996,39177,39178],{},"outlet(0, \"set\", json)"," - The \"set\" message tells ",[895,39181,38071],{}," to display the text without triggering its output.",[1074,39184,39186],{"id":39185},"viewing-the-output","Viewing the Output",[842,39188,39189],{},"The JSON output appears in the Max Console window:",[1045,39191,31345,39192],{"style":38079},[38081,39193,38084,39194,38084,39199,31345],{"style":38083},[1027,39195],{"src":39196,"alt":39197,"style":39198},"/images/blog/musictechlab_blog_ableton-locators-json-console.webp","Max Console showing JSON export output","max-width: 480px; width: 100%; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: block;",[38091,39200,39201],{"style":38093},"Max Console displaying the exported JSON with BPM and sections",[863,39203,39205],{"id":39204},"troubleshooting-common-issues","Troubleshooting Common Issues",[1074,39207,39209],{"id":39208},"js-cant-find-file-marker_exportjs","\"js: can't find file marker_export.js\"",[842,39211,39212],{},"Max can't locate your JavaScript file.",[842,39214,39215],{},[996,39216,39217],{},"Solution:",[991,39219,39220,39227,39230,39236],{},[961,39221,39222,39223,39226],{},"Right-click on the ",[895,39224,39225],{},"js"," object in Max",[961,39228,39229],{},"Select \"Open marker_export.js\"",[961,39231,39232,39233,26554],{},"Save As... to the same folder as your ",[895,39234,39235],{},".amxd",[961,39237,39238,39239,39241],{},"Ensure the object text is just ",[895,39240,38065],{}," (no paths)",[1074,39243,39245],{"id":39244},"js-no-function-msg_int","\"js: no function msg_int\"",[842,39247,39248],{},"The button sends an integer, but your script doesn't have a handler for it.",[842,39250,39251,39253],{},[996,39252,39217],{}," Add this function to your script:",[1013,39255,39257],{"className":33433,"code":39256,"language":33435,"meta":728,"style":728},"function msg_int(v) {\n  if (v === 1) bang();\n}\n",[895,39258,39259,39273,39293],{"__ignoreMap":728},[1086,39260,39261,39263,39265,39267,39269,39271],{"class":1088,"line":1089},[1086,39262,35934],{"class":1155},[1086,39264,38266],{"class":1105},[1086,39266,1398],{"class":1146},[1086,39268,38271],{"class":1401},[1086,39270,1410],{"class":1146},[1086,39272,1164],{"class":1146},[1086,39274,39275,39277,39279,39281,39283,39285,39287,39289,39291],{"class":1088,"line":729},[1086,39276,27233],{"class":1423},[1086,39278,5979],{"class":4109},[1086,39280,38271],{"class":1436},[1086,39282,27244],{"class":1146},[1086,39284,23488],{"class":1187},[1086,39286,2778],{"class":4109},[1086,39288,38292],{"class":1105},[1086,39290,2516],{"class":4109},[1086,39292,33466],{"class":1146},[1086,39294,39295],{"class":1088,"line":1112},[1086,39296,1291],{"class":1146},[1074,39298,39300],{"id":39299},"song-object-has-no-attribute-locators","\"Song object has no attribute 'locators'\"",[842,39302,39303],{},"The LiveAPI path varies between Ableton versions.",[842,39305,39306,34136,39308,39310],{},[996,39307,39217],{},[895,39309,36105],{}," instead:",[1013,39312,39314],{"className":33433,"code":39313,"language":33435,"meta":728,"style":728},"var locatorIds = song.get(\"cue_points\");\n",[895,39315,39316],{"__ignoreMap":728},[1086,39317,39318,39320,39323,39325,39327,39329,39331,39333,39335,39337,39339,39341],{"class":1088,"line":1089},[1086,39319,38651],{"class":1155},[1086,39321,39322],{"class":1436}," locatorIds ",[1086,39324,1440],{"class":1146},[1086,39326,35983],{"class":1436},[1086,39328,861],{"class":1146},[1086,39330,10812],{"class":1105},[1086,39332,1398],{"class":1436},[1086,39334,1159],{"class":1146},[1086,39336,36105],{"class":1096},[1086,39338,1159],{"class":1146},[1086,39340,1410],{"class":1436},[1086,39342,33466],{"class":1146},[1074,39344,39346],{"id":39345},"sendmessage-error-2-bad-parameter-value","\"SendMessage error 2: Bad parameter value\"",[1045,39348,31345,39349],{"style":38079},[38081,39350,38084,39351,38084,39356,31345],{"style":38083},[1027,39352],{"src":39353,"alt":39354,"style":39355},"/images/blog/musictechlab_blog_ableton-locators-json-error.webp","SendMessage error in Max Console","max-width: 400px; width: 100%; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: block;",[38091,39357,39358],{"style":38093},"Typical SendMessage errors when LiveAPI path is incorrect",[842,39360,39361],{},"Usually caused by:",[958,39363,39364,39367,39370],{},[961,39365,39366],{},"Invalid LiveAPI path",[961,39368,39369],{},"Querying a property that doesn't exist",[961,39371,39372],{},"Non-numeric values in the locator ID array",[842,39374,39375,39377],{},[996,39376,39217],{}," Filter IDs properly:",[1013,39379,39381],{"className":33433,"code":39380,"language":33435,"meta":728,"style":728},"if (typeof locatorIds[i] !== \"number\") continue;\n",[895,39382,39383],{"__ignoreMap":728},[1086,39384,39385,39387,39389,39391,39394,39396,39398,39400,39402,39404,39406],{"class":1088,"line":1089},[1086,39386,11056],{"class":1423},[1086,39388,5979],{"class":1436},[1086,39390,38691],{"class":1146},[1086,39392,39393],{"class":1436}," locatorIds[i] ",[1086,39395,38702],{"class":1146},[1086,39397,1195],{"class":1146},[1086,39399,18485],{"class":1096},[1086,39401,1159],{"class":1146},[1086,39403,2778],{"class":1436},[1086,39405,38713],{"class":1423},[1086,39407,33466],{"class":1146},[863,39409,39411],{"id":39410},"adding-file-export-optional","Adding File Export (Optional)",[842,39413,39414],{},"To save the JSON to disk, extend the script:",[1013,39416,39418],{"className":33433,"code":39417,"language":33435,"meta":728,"style":728},"// --- FILE SAVE ---\nvar folder = \"~/Documents/AbletonExports/\";\nvar f = new File(folder + name + \".json\", \"write\");\n\nif (f.isopen) {\n  f.writestring(json);\n  f.close();\n  post(\"Saved to:\", folder + name + \".json\", \"\\\\n\");\n} else {\n  post(\"EXPORT ERROR: cannot write file\\\\n\");\n}\n",[895,39419,39420,39425,39443,39485,39489,39503,39521,39534,39579,39587,39608],{"__ignoreMap":728},[1086,39421,39422],{"class":1088,"line":1089},[1086,39423,39424],{"class":1427},"// --- FILE SAVE ---\n",[1086,39426,39427,39429,39432,39434,39436,39439,39441],{"class":1088,"line":729},[1086,39428,38651],{"class":1155},[1086,39430,39431],{"class":1436}," folder ",[1086,39433,1440],{"class":1146},[1086,39435,1195],{"class":1146},[1086,39437,39438],{"class":1096},"~/Documents/AbletonExports/",[1086,39440,1159],{"class":1146},[1086,39442,33466],{"class":1146},[1086,39444,39445,39447,39450,39452,39454,39456,39459,39461,39464,39466,39468,39471,39473,39475,39477,39479,39481,39483],{"class":1088,"line":1112},[1086,39446,38651],{"class":1155},[1086,39448,39449],{"class":1436}," f ",[1086,39451,1440],{"class":1146},[1086,39453,26591],{"class":1146},[1086,39455,26433],{"class":1105},[1086,39457,39458],{"class":1436},"(folder ",[1086,39460,30708],{"class":1146},[1086,39462,39463],{"class":1436}," name ",[1086,39465,30708],{"class":1146},[1086,39467,1195],{"class":1146},[1086,39469,39470],{"class":1096},".json",[1086,39472,1159],{"class":1146},[1086,39474,1227],{"class":1146},[1086,39476,1195],{"class":1146},[1086,39478,29046],{"class":1096},[1086,39480,1159],{"class":1146},[1086,39482,1410],{"class":1436},[1086,39484,33466],{"class":1146},[1086,39486,39487],{"class":1088,"line":1181},[1086,39488,3390],{"emptyLinePlaceholder":738},[1086,39490,39491,39493,39496,39498,39501],{"class":1088,"line":1205},[1086,39492,11056],{"class":1423},[1086,39494,39495],{"class":1436}," (f",[1086,39497,861],{"class":1146},[1086,39499,39500],{"class":1436},"isopen) ",[1086,39502,1147],{"class":1146},[1086,39504,39505,39508,39510,39513,39515,39517,39519],{"class":1088,"line":1276},[1086,39506,39507],{"class":1436},"  f",[1086,39509,861],{"class":1146},[1086,39511,39512],{"class":1105},"writestring",[1086,39514,1398],{"class":4109},[1086,39516,1139],{"class":1436},[1086,39518,1410],{"class":4109},[1086,39520,33466],{"class":1146},[1086,39522,39523,39525,39527,39530,39532],{"class":1088,"line":1282},[1086,39524,39507],{"class":1436},[1086,39526,861],{"class":1146},[1086,39528,39529],{"class":1105},"close",[1086,39531,2516],{"class":4109},[1086,39533,33466],{"class":1146},[1086,39535,39536,39539,39541,39543,39546,39548,39550,39553,39555,39557,39559,39561,39563,39565,39567,39569,39571,39573,39575,39577],{"class":1088,"line":1288},[1086,39537,39538],{"class":1105},"  post",[1086,39540,1398],{"class":4109},[1086,39542,1159],{"class":1146},[1086,39544,39545],{"class":1096},"Saved to:",[1086,39547,1159],{"class":1146},[1086,39549,1227],{"class":1146},[1086,39551,39552],{"class":1436}," folder",[1086,39554,33787],{"class":1146},[1086,39556,24696],{"class":1436},[1086,39558,33787],{"class":1146},[1086,39560,1195],{"class":1146},[1086,39562,39470],{"class":1096},[1086,39564,1159],{"class":1146},[1086,39566,1227],{"class":1146},[1086,39568,1195],{"class":1146},[1086,39570,39056],{"class":1436},[1086,39572,39059],{"class":1096},[1086,39574,1159],{"class":1146},[1086,39576,1410],{"class":4109},[1086,39578,33466],{"class":1146},[1086,39580,39581,39583,39585],{"class":1088,"line":2685},[1086,39582,4423],{"class":1146},[1086,39584,36749],{"class":1423},[1086,39586,1164],{"class":1146},[1086,39588,39589,39591,39593,39595,39598,39600,39602,39604,39606],{"class":1088,"line":2700},[1086,39590,39538],{"class":1105},[1086,39592,1398],{"class":4109},[1086,39594,1159],{"class":1146},[1086,39596,39597],{"class":1096},"EXPORT ERROR: cannot write file",[1086,39599,39056],{"class":1436},[1086,39601,39059],{"class":1096},[1086,39603,1159],{"class":1146},[1086,39605,1410],{"class":4109},[1086,39607,33466],{"class":1146},[1086,39609,39610],{"class":1088,"line":3398},[1086,39611,1291],{"class":1146},[842,39613,39614,8774,39617,39620],{},[996,39615,39616],{},"Note:",[895,39618,39619],{},"~"," path expansion can be inconsistent in Max/JS. For reliability, either:",[958,39622,39623,39626,39629],{},[961,39624,39625],{},"Use absolute paths",[961,39627,39628],{},"Save to the project folder",[961,39630,39631],{},"Create the target directory manually first",[863,39633,39635],{"id":39634},"packaging-for-distribution","Packaging for Distribution",[1074,39637,39639],{"id":39638},"the-simple-way-recommended-for-mvp","The Simple Way (Recommended for MVP)",[842,39641,39642],{},"Create a folder with both files:",[1013,39644,39647],{"className":39645,"code":39646,"language":1018},[1016],"MTL_LocatorsToJSON/\n├── MTL_LocatorsToJSON.amxd\n├── marker_export.js\n└── README.txt\n",[895,39648,39646],{"__ignoreMap":728},[842,39650,39651],{},"Compress to ZIP and share. Recipients should:",[991,39653,39654,39657,39663],{},[961,39655,39656],{},"Unzip the folder",[961,39658,39659,39660],{},"Copy to: ",[895,39661,39662],{},"~/Music/Ableton/User Library/Presets/MIDI Effects/Max MIDI Effect/",[961,39664,39665],{},"Restart Ableton Live (or refresh the Browser)",[1074,39667,39669],{"id":39668},"why-not-alp-ableton-pack","Why Not .alp (Ableton Pack)?",[1045,39671,31345,39672],{"style":38079},[38081,39673,38084,39674,38084,39679,31345],{"style":38083},[1027,39675],{"src":39676,"alt":39677,"style":39678},"/images/blog/musictechlab_blog_ableton-locators-json-pack-error.webp","Ableton Pack error - This is not a valid Pack","max-width: 500px; width: 100%; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: block;",[38091,39680,39681],{"style":38093},"\"This is not a valid Pack\" - why we use ZIP instead of .alp",[842,39683,5119,39684,39687,39688,39691],{},[895,39685,39686],{},".alp"," format is ",[996,39689,39690],{},"not"," a simple ZIP with a different extension. Creating valid Packs requires Ableton's specific export workflow, which varies between Live versions. For quick distribution, a ZIP folder is more reliable.",[1074,39693,39695],{"id":39694},"project-folder-structure","Project Folder Structure",[1045,39697,31345,39698],{"style":38079},[38081,39699,38084,39700,38084,39704,31345],{"style":38083},[1027,39701],{"src":39702,"alt":39703,"style":39355},"/images/blog/musictechlab_blog_ableton-locators-json-folder.webp","Finder showing the project folder structure",[38091,39705,39706],{"style":38093},"Project folder with the .amxd device and JS file",[863,39708,39710],{"id":39709},"usage-instructions","Usage Instructions",[1045,39712,31345,39713],{"style":38079},[38081,39714,38084,39715,38084,39720,31345],{"style":38083},[1027,39716],{"src":39717,"alt":39718,"style":39719},"/images/blog/musictechlab_blog_ableton-locators-json-browser.webp","Device visible in Ableton Live browser","max-width: 360px; width: 100%; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: block;",[38091,39721,39722],{"style":38093},"The device appears in Ableton's Browser under Max MIDI Effect",[991,39724,39725,39731,39737,39743,39748],{},[961,39726,39727,39730],{},[996,39728,39729],{},"Add the device"," to any MIDI track",[961,39732,39733,39736],{},[996,39734,39735],{},"Create locators"," in the Arrangement view (right-click → Add Locator, or Ctrl/Cmd+Shift+L)",[961,39738,39739,39742],{},[996,39740,39741],{},"Name your locators"," (INTRO, VERSE, CHORUS, etc.)",[961,39744,39745],{},[996,39746,39747],{},"Click EXPORT JSON",[961,39749,39750,39753],{},[996,39751,39752],{},"Copy the JSON"," from the text display or find it in your export folder",[863,39755,39757],{"id":39756},"integration-with-ai-workflows","Integration with AI Workflows",[842,39759,39760,39761,39764],{},"This JSON format is designed to be compatible with our ",[846,39762,39763],{"href":101},"song structure analysis pipeline",". You can:",[991,39766,39767,39772,39777,39783],{},[961,39768,39769,39771],{},[996,39770,3626],{}," manually-annotated structures from Ableton",[961,39773,39774,39776],{},[996,39775,33961],{}," with AI-detected structures",[961,39778,39779,39782],{},[996,39780,39781],{},"Train"," custom models on your annotations",[961,39784,39785,39788],{},[996,39786,39787],{},"Import"," AI-generated structures back into your DAW",[863,39790,31693],{"id":16446},[842,39792,39793],{},"This MVP opens several possibilities:",[871,39795,39796,39805],{},[874,39797,39798],{},[877,39799,39800,39803],{},[880,39801,39802],{},"Enhancement",[880,39804,7624],{},[887,39806,39807,39817,39827,39837,39847],{},[877,39808,39809,39814],{},[892,39810,39811],{},[996,39812,39813],{},"Bidirectional sync",[892,39815,39816],{},"Import JSON to create locators",[877,39818,39819,39824],{},[892,39820,39821],{},[996,39822,39823],{},"Audio Effect version",[892,39825,39826],{},"Support audio tracks, not just MIDI",[877,39828,39829,39834],{},[892,39830,39831],{},[996,39832,39833],{},"Real-time export",[892,39835,39836],{},"Auto-export on locator changes",[877,39838,39839,39844],{},[892,39840,39841],{},[996,39842,39843],{},"Cloud sync",[892,39845,39846],{},"Push to API endpoint directly",[877,39848,39849,39854],{},[892,39850,39851],{},[996,39852,39853],{},"Time signature support",[892,39855,39856],{},"Include meter changes",[863,39858,39860],{"id":39859},"complete-code-reference","Complete Code Reference",[842,39862,39863],{},"The final working script:",[1013,39865,39867],{"className":33433,"code":39866,"language":33435,"meta":728,"style":728},"autowatch = 1;\noutlets = 1;\n\nfunction msg_int(v) {\n  if (v === 1) bang();\n}\n\nfunction bang() {\n  try {\n    var song = new LiveAPI(\"live_set\");\n\n    // PROJECT NAME\n    var name = song.get(\"name\");\n    name = Array.isArray(name) ? name[0] : name;\n    if (!name || name === \"\") name = \"untitled\";\n    name = name.replace(/[^a-z0-9_\\-]/gi, \"_\");\n\n    // BPM\n    var bpm = song.get(\"tempo\");\n    bpm = Array.isArray(bpm) ? bpm[0] : bpm;\n\n    // LOCATORS\n    var locatorIds = song.get(\"cue_points\");\n    var sections = [];\n\n    if (Array.isArray(locatorIds)) {\n      for (var i = 0; i \u003C locatorIds.length; i++) {\n        if (typeof locatorIds[i] !== \"number\") continue;\n\n        var l = new LiveAPI(\"id \" + locatorIds[i]);\n        var label = l.get(\"name\");\n        var time = l.get(\"time\");\n\n        sections.push({\n          label: Array.isArray(label) ? label[0] : label,\n          time_seconds: Array.isArray(time) ? time[0] : time\n        });\n      }\n    }\n\n    // JSON OUTPUT\n    var data = {\n      project: name,\n      bpm: bpm,\n      sections: sections\n    };\n\n    var json = JSON.stringify(data, null, 2);\n    outlet(0, \"set\", json);\n\n    // OPTIONAL: File save\n    var folder = \"~/Documents/AbletonExports/\";\n    var f = new File(folder + name + \".json\", \"write\");\n    if (f.isopen) {\n      f.writestring(json);\n      f.close();\n    }\n\n  } catch (e) {\n    post(\"EXPORT ERROR:\", e.toString(), \"\\\\n\");\n  }\n}\n",[895,39868,39869,39879,39889,39893,39907,39927,39931,39935,39945,39951,39975,39979,39984,40010,40044,40076,40112,40116,40121,40147,40181,40185,40190,40216,40228,40232,40252,40288,40318,40322,40354,40380,40406,40410,40422,40456,40488,40496,40500,40504,40508,40513,40523,40533,40543,40551,40555,40559,40587,40611,40615,40620,40636,40677,40694,40711,40723,40727,40731,40745,40781,40785],{"__ignoreMap":728},[1086,39870,39871,39873,39875,39877],{"class":1088,"line":1089},[1086,39872,38238],{"class":1436},[1086,39874,1440],{"class":1146},[1086,39876,23488],{"class":1187},[1086,39878,33466],{"class":1146},[1086,39880,39881,39883,39885,39887],{"class":1088,"line":729},[1086,39882,38249],{"class":1436},[1086,39884,1440],{"class":1146},[1086,39886,23488],{"class":1187},[1086,39888,33466],{"class":1146},[1086,39890,39891],{"class":1088,"line":1112},[1086,39892,3390],{"emptyLinePlaceholder":738},[1086,39894,39895,39897,39899,39901,39903,39905],{"class":1088,"line":1181},[1086,39896,35934],{"class":1155},[1086,39898,38266],{"class":1105},[1086,39900,1398],{"class":1146},[1086,39902,38271],{"class":1401},[1086,39904,1410],{"class":1146},[1086,39906,1164],{"class":1146},[1086,39908,39909,39911,39913,39915,39917,39919,39921,39923,39925],{"class":1088,"line":1205},[1086,39910,27233],{"class":1423},[1086,39912,5979],{"class":4109},[1086,39914,38271],{"class":1436},[1086,39916,27244],{"class":1146},[1086,39918,23488],{"class":1187},[1086,39920,2778],{"class":4109},[1086,39922,38292],{"class":1105},[1086,39924,2516],{"class":4109},[1086,39926,33466],{"class":1146},[1086,39928,39929],{"class":1088,"line":1276},[1086,39930,1291],{"class":1146},[1086,39932,39933],{"class":1088,"line":1282},[1086,39934,3390],{"emptyLinePlaceholder":738},[1086,39936,39937,39939,39941,39943],{"class":1088,"line":1288},[1086,39938,35934],{"class":1155},[1086,39940,35937],{"class":1105},[1086,39942,2516],{"class":1146},[1086,39944,1164],{"class":1146},[1086,39946,39947,39949],{"class":1088,"line":2685},[1086,39948,38319],{"class":1423},[1086,39950,1164],{"class":1146},[1086,39952,39953,39955,39957,39959,39961,39963,39965,39967,39969,39971,39973],{"class":1088,"line":2700},[1086,39954,38326],{"class":1155},[1086,39956,35983],{"class":1436},[1086,39958,19552],{"class":1146},[1086,39960,26591],{"class":1146},[1086,39962,35958],{"class":1105},[1086,39964,1398],{"class":4109},[1086,39966,1159],{"class":1146},[1086,39968,35965],{"class":1096},[1086,39970,1159],{"class":1146},[1086,39972,1410],{"class":4109},[1086,39974,33466],{"class":1146},[1086,39976,39977],{"class":1088,"line":3398},[1086,39978,3390],{"emptyLinePlaceholder":738},[1086,39980,39981],{"class":1088,"line":1715},[1086,39982,39983],{"class":1427},"    // PROJECT NAME\n",[1086,39985,39986,39988,39990,39992,39994,39996,39998,40000,40002,40004,40006,40008],{"class":1088,"line":3409},[1086,39987,38326],{"class":1155},[1086,39989,24696],{"class":1436},[1086,39991,19552],{"class":1146},[1086,39993,35983],{"class":1436},[1086,39995,861],{"class":1146},[1086,39997,10812],{"class":1105},[1086,39999,1398],{"class":4109},[1086,40001,1159],{"class":1146},[1086,40003,4184],{"class":1096},[1086,40005,1159],{"class":1146},[1086,40007,1410],{"class":4109},[1086,40009,33466],{"class":1146},[1086,40011,40012,40014,40016,40018,40020,40022,40024,40026,40028,40030,40032,40034,40036,40038,40040,40042],{"class":1088,"line":3415},[1086,40013,38386],{"class":1436},[1086,40015,19552],{"class":1146},[1086,40017,38391],{"class":1436},[1086,40019,861],{"class":1146},[1086,40021,38396],{"class":1105},[1086,40023,1398],{"class":4109},[1086,40025,4184],{"class":1436},[1086,40027,2778],{"class":4109},[1086,40029,38405],{"class":1146},[1086,40031,24696],{"class":1436},[1086,40033,4340],{"class":4109},[1086,40035,4417],{"class":1187},[1086,40037,33549],{"class":4109},[1086,40039,1133],{"class":1146},[1086,40041,24696],{"class":1436},[1086,40043,33466],{"class":1146},[1086,40045,40046,40048,40050,40052,40054,40056,40058,40060,40062,40064,40066,40068,40070,40072,40074],{"class":1088,"line":3421},[1086,40047,6474],{"class":1423},[1086,40049,5979],{"class":4109},[1086,40051,38428],{"class":1146},[1086,40053,4184],{"class":1436},[1086,40055,38433],{"class":1146},[1086,40057,24696],{"class":1436},[1086,40059,27244],{"class":1146},[1086,40061,19571],{"class":1146},[1086,40063,2778],{"class":4109},[1086,40065,4184],{"class":1436},[1086,40067,19552],{"class":1146},[1086,40069,1195],{"class":1146},[1086,40071,38450],{"class":1096},[1086,40073,1159],{"class":1146},[1086,40075,33466],{"class":1146},[1086,40077,40078,40080,40082,40084,40086,40088,40090,40092,40094,40096,40098,40100,40102,40104,40106,40108,40110],{"class":1088,"line":3427},[1086,40079,38386],{"class":1436},[1086,40081,19552],{"class":1146},[1086,40083,24696],{"class":1436},[1086,40085,861],{"class":1146},[1086,40087,38467],{"class":1105},[1086,40089,1398],{"class":4109},[1086,40091,38472],{"class":1146},[1086,40093,38475],{"class":1096},[1086,40095,38478],{"class":1436},[1086,40097,38481],{"class":1146},[1086,40099,38484],{"class":1187},[1086,40101,1227],{"class":1146},[1086,40103,1195],{"class":1146},[1086,40105,25919],{"class":1096},[1086,40107,1159],{"class":1146},[1086,40109,1410],{"class":4109},[1086,40111,33466],{"class":1146},[1086,40113,40114],{"class":1088,"line":3433},[1086,40115,3390],{"emptyLinePlaceholder":738},[1086,40117,40118],{"class":1088,"line":3439},[1086,40119,40120],{"class":1427},"    // BPM\n",[1086,40122,40123,40125,40127,40129,40131,40133,40135,40137,40139,40141,40143,40145],{"class":1088,"line":3444},[1086,40124,38326],{"class":1155},[1086,40126,36275],{"class":1436},[1086,40128,19552],{"class":1146},[1086,40130,35983],{"class":1436},[1086,40132,861],{"class":1146},[1086,40134,10812],{"class":1105},[1086,40136,1398],{"class":4109},[1086,40138,1159],{"class":1146},[1086,40140,36021],{"class":1096},[1086,40142,1159],{"class":1146},[1086,40144,1410],{"class":4109},[1086,40146,33466],{"class":1146},[1086,40148,40149,40151,40153,40155,40157,40159,40161,40163,40165,40167,40169,40171,40173,40175,40177,40179],{"class":1088,"line":3450},[1086,40150,36270],{"class":1436},[1086,40152,19552],{"class":1146},[1086,40154,38391],{"class":1436},[1086,40156,861],{"class":1146},[1086,40158,38396],{"class":1105},[1086,40160,1398],{"class":4109},[1086,40162,31015],{"class":1436},[1086,40164,2778],{"class":4109},[1086,40166,38405],{"class":1146},[1086,40168,36275],{"class":1436},[1086,40170,4340],{"class":4109},[1086,40172,4417],{"class":1187},[1086,40174,33549],{"class":4109},[1086,40176,1133],{"class":1146},[1086,40178,36275],{"class":1436},[1086,40180,33466],{"class":1146},[1086,40182,40183],{"class":1088,"line":3456},[1086,40184,3390],{"emptyLinePlaceholder":738},[1086,40186,40187],{"class":1088,"line":3462},[1086,40188,40189],{"class":1427},"    // LOCATORS\n",[1086,40191,40192,40194,40196,40198,40200,40202,40204,40206,40208,40210,40212,40214],{"class":1088,"line":3467},[1086,40193,38326],{"class":1155},[1086,40195,38581],{"class":1436},[1086,40197,19552],{"class":1146},[1086,40199,35983],{"class":1436},[1086,40201,861],{"class":1146},[1086,40203,10812],{"class":1105},[1086,40205,1398],{"class":4109},[1086,40207,1159],{"class":1146},[1086,40209,36105],{"class":1096},[1086,40211,1159],{"class":1146},[1086,40213,1410],{"class":4109},[1086,40215,33466],{"class":1146},[1086,40217,40218,40220,40222,40224,40226],{"class":1088,"line":3473},[1086,40219,38326],{"class":1155},[1086,40221,36315],{"class":1436},[1086,40223,19552],{"class":1146},[1086,40225,38612],{"class":4109},[1086,40227,33466],{"class":1146},[1086,40229,40230],{"class":1088,"line":3479},[1086,40231,3390],{"emptyLinePlaceholder":738},[1086,40233,40234,40236,40238,40240,40242,40244,40246,40248,40250],{"class":1088,"line":3485},[1086,40235,6474],{"class":1423},[1086,40237,5979],{"class":4109},[1086,40239,38627],{"class":1436},[1086,40241,861],{"class":1146},[1086,40243,38396],{"class":1105},[1086,40245,1398],{"class":4109},[1086,40247,38636],{"class":1436},[1086,40249,38639],{"class":4109},[1086,40251,1147],{"class":1146},[1086,40253,40254,40256,40258,40260,40262,40264,40266,40268,40270,40272,40274,40276,40278,40280,40282,40284,40286],{"class":1088,"line":3491},[1086,40255,38646],{"class":1423},[1086,40257,5979],{"class":4109},[1086,40259,38651],{"class":1155},[1086,40261,33757],{"class":1436},[1086,40263,19552],{"class":1146},[1086,40265,22718],{"class":1187},[1086,40267,4639],{"class":1146},[1086,40269,33757],{"class":1436},[1086,40271,38664],{"class":1146},[1086,40273,38581],{"class":1436},[1086,40275,861],{"class":1146},[1086,40277,38671],{"class":1436},[1086,40279,4639],{"class":1146},[1086,40281,33757],{"class":1436},[1086,40283,38678],{"class":1146},[1086,40285,2778],{"class":4109},[1086,40287,1147],{"class":1146},[1086,40289,40290,40292,40294,40296,40298,40300,40302,40304,40306,40308,40310,40312,40314,40316],{"class":1088,"line":3497},[1086,40291,6800],{"class":1423},[1086,40293,5979],{"class":4109},[1086,40295,38691],{"class":1146},[1086,40297,38581],{"class":1436},[1086,40299,4340],{"class":4109},[1086,40301,33784],{"class":1436},[1086,40303,33549],{"class":4109},[1086,40305,38702],{"class":1146},[1086,40307,1195],{"class":1146},[1086,40309,18485],{"class":1096},[1086,40311,1159],{"class":1146},[1086,40313,2778],{"class":4109},[1086,40315,38713],{"class":1423},[1086,40317,33466],{"class":1146},[1086,40319,40320],{"class":1088,"line":3503},[1086,40321,3390],{"emptyLinePlaceholder":738},[1086,40323,40324,40326,40328,40330,40332,40334,40336,40338,40340,40342,40344,40346,40348,40350,40352],{"class":1088,"line":3509},[1086,40325,38724],{"class":1155},[1086,40327,38727],{"class":1436},[1086,40329,19552],{"class":1146},[1086,40331,26591],{"class":1146},[1086,40333,35958],{"class":1105},[1086,40335,1398],{"class":4109},[1086,40337,1159],{"class":1146},[1086,40339,36153],{"class":1096},[1086,40341,1159],{"class":1146},[1086,40343,33787],{"class":1146},[1086,40345,38581],{"class":1436},[1086,40347,4340],{"class":4109},[1086,40349,33784],{"class":1436},[1086,40351,20587],{"class":4109},[1086,40353,33466],{"class":1146},[1086,40355,40356,40358,40360,40362,40364,40366,40368,40370,40372,40374,40376,40378],{"class":1088,"line":3515},[1086,40357,38724],{"class":1155},[1086,40359,38760],{"class":1436},[1086,40361,19552],{"class":1146},[1086,40363,38727],{"class":1436},[1086,40365,861],{"class":1146},[1086,40367,10812],{"class":1105},[1086,40369,1398],{"class":4109},[1086,40371,1159],{"class":1146},[1086,40373,4184],{"class":1096},[1086,40375,1159],{"class":1146},[1086,40377,1410],{"class":4109},[1086,40379,33466],{"class":1146},[1086,40381,40382,40384,40386,40388,40390,40392,40394,40396,40398,40400,40402,40404],{"class":1088,"line":3520},[1086,40383,38724],{"class":1155},[1086,40385,38787],{"class":1436},[1086,40387,19552],{"class":1146},[1086,40389,38727],{"class":1436},[1086,40391,861],{"class":1146},[1086,40393,10812],{"class":1105},[1086,40395,1398],{"class":4109},[1086,40397,1159],{"class":1146},[1086,40399,31109],{"class":1096},[1086,40401,1159],{"class":1146},[1086,40403,1410],{"class":4109},[1086,40405,33466],{"class":1146},[1086,40407,40408],{"class":1088,"line":3526},[1086,40409,3390],{"emptyLinePlaceholder":738},[1086,40411,40412,40414,40416,40418,40420],{"class":1088,"line":3531},[1086,40413,37058],{"class":1436},[1086,40415,861],{"class":1146},[1086,40417,36171],{"class":1105},[1086,40419,1398],{"class":4109},[1086,40421,1147],{"class":1146},[1086,40423,40424,40426,40428,40430,40432,40434,40436,40438,40440,40442,40444,40446,40448,40450,40452,40454],{"class":1088,"line":3537},[1086,40425,38828],{"class":4109},[1086,40427,1133],{"class":1146},[1086,40429,38391],{"class":1436},[1086,40431,861],{"class":1146},[1086,40433,38396],{"class":1105},[1086,40435,1398],{"class":4109},[1086,40437,10840],{"class":1436},[1086,40439,2778],{"class":4109},[1086,40441,38405],{"class":1146},[1086,40443,38760],{"class":1436},[1086,40445,4340],{"class":4109},[1086,40447,4417],{"class":1187},[1086,40449,33549],{"class":4109},[1086,40451,1133],{"class":1146},[1086,40453,38760],{"class":1436},[1086,40455,1202],{"class":1146},[1086,40457,40458,40460,40462,40464,40466,40468,40470,40472,40474,40476,40478,40480,40482,40484,40486],{"class":1088,"line":3543},[1086,40459,38863],{"class":4109},[1086,40461,1133],{"class":1146},[1086,40463,38391],{"class":1436},[1086,40465,861],{"class":1146},[1086,40467,38396],{"class":1105},[1086,40469,1398],{"class":4109},[1086,40471,31109],{"class":1436},[1086,40473,2778],{"class":4109},[1086,40475,38405],{"class":1146},[1086,40477,38787],{"class":1436},[1086,40479,4340],{"class":4109},[1086,40481,4417],{"class":1187},[1086,40483,33549],{"class":4109},[1086,40485,1133],{"class":1146},[1086,40487,38892],{"class":1436},[1086,40489,40490,40492,40494],{"class":1088,"line":3549},[1086,40491,38897],{"class":1146},[1086,40493,1410],{"class":4109},[1086,40495,33466],{"class":1146},[1086,40497,40498],{"class":1088,"line":3555},[1086,40499,26783],{"class":1146},[1086,40501,40502],{"class":1088,"line":3561},[1086,40503,1279],{"class":1146},[1086,40505,40506],{"class":1088,"line":3567},[1086,40507,3390],{"emptyLinePlaceholder":738},[1086,40509,40510],{"class":1088,"line":17749},[1086,40511,40512],{"class":1427},"    // JSON OUTPUT\n",[1086,40514,40515,40517,40519,40521],{"class":1088,"line":19843},[1086,40516,38326],{"class":1155},[1086,40518,20125],{"class":1436},[1086,40520,19552],{"class":1146},[1086,40522,1164],{"class":1146},[1086,40524,40525,40527,40529,40531],{"class":1088,"line":19848},[1086,40526,38933],{"class":4109},[1086,40528,1133],{"class":1146},[1086,40530,24696],{"class":1436},[1086,40532,1202],{"class":1146},[1086,40534,40535,40537,40539,40541],{"class":1088,"line":19880},[1086,40536,38944],{"class":4109},[1086,40538,1133],{"class":1146},[1086,40540,36275],{"class":1436},[1086,40542,1202],{"class":1146},[1086,40544,40545,40547,40549],{"class":1088,"line":19896},[1086,40546,38955],{"class":4109},[1086,40548,1133],{"class":1146},[1086,40550,38960],{"class":1436},[1086,40552,40553],{"class":1088,"line":19919},[1086,40554,38965],{"class":1146},[1086,40556,40557],{"class":1088,"line":19927},[1086,40558,3390],{"emptyLinePlaceholder":738},[1086,40560,40561,40563,40565,40567,40569,40571,40573,40575,40577,40579,40581,40583,40585],{"class":1088,"line":19948},[1086,40562,38326],{"class":1155},[1086,40564,1463],{"class":1436},[1086,40566,19552],{"class":1146},[1086,40568,26986],{"class":1436},[1086,40570,861],{"class":1146},[1086,40572,26991],{"class":1105},[1086,40574,1398],{"class":4109},[1086,40576,4337],{"class":1436},[1086,40578,1227],{"class":1146},[1086,40580,38992],{"class":1146},[1086,40582,9048],{"class":1187},[1086,40584,1410],{"class":4109},[1086,40586,33466],{"class":1146},[1086,40588,40589,40591,40593,40595,40597,40599,40601,40603,40605,40607,40609],{"class":1088,"line":19968},[1086,40590,39012],{"class":1105},[1086,40592,1398],{"class":4109},[1086,40594,4417],{"class":1187},[1086,40596,1227],{"class":1146},[1086,40598,1195],{"class":1146},[1086,40600,20205],{"class":1096},[1086,40602,1159],{"class":1146},[1086,40604,1227],{"class":1146},[1086,40606,1463],{"class":1436},[1086,40608,1410],{"class":4109},[1086,40610,33466],{"class":1146},[1086,40612,40613],{"class":1088,"line":19987},[1086,40614,3390],{"emptyLinePlaceholder":738},[1086,40616,40617],{"class":1088,"line":20007},[1086,40618,40619],{"class":1427},"    // OPTIONAL: File save\n",[1086,40621,40622,40624,40626,40628,40630,40632,40634],{"class":1088,"line":20013},[1086,40623,38326],{"class":1155},[1086,40625,39552],{"class":1436},[1086,40627,19552],{"class":1146},[1086,40629,1195],{"class":1146},[1086,40631,39438],{"class":1096},[1086,40633,1159],{"class":1146},[1086,40635,33466],{"class":1146},[1086,40637,40638,40640,40642,40644,40646,40648,40650,40653,40655,40657,40659,40661,40663,40665,40667,40669,40671,40673,40675],{"class":1088,"line":20021},[1086,40639,38326],{"class":1155},[1086,40641,4403],{"class":1436},[1086,40643,19552],{"class":1146},[1086,40645,26591],{"class":1146},[1086,40647,26433],{"class":1105},[1086,40649,1398],{"class":4109},[1086,40651,40652],{"class":1436},"folder",[1086,40654,33787],{"class":1146},[1086,40656,24696],{"class":1436},[1086,40658,33787],{"class":1146},[1086,40660,1195],{"class":1146},[1086,40662,39470],{"class":1096},[1086,40664,1159],{"class":1146},[1086,40666,1227],{"class":1146},[1086,40668,1195],{"class":1146},[1086,40670,29046],{"class":1096},[1086,40672,1159],{"class":1146},[1086,40674,1410],{"class":4109},[1086,40676,33466],{"class":1146},[1086,40678,40679,40681,40683,40685,40687,40690,40692],{"class":1088,"line":20051},[1086,40680,6474],{"class":1423},[1086,40682,5979],{"class":4109},[1086,40684,5962],{"class":1436},[1086,40686,861],{"class":1146},[1086,40688,40689],{"class":1436},"isopen",[1086,40691,2778],{"class":4109},[1086,40693,1147],{"class":1146},[1086,40695,40696,40699,40701,40703,40705,40707,40709],{"class":1088,"line":20072},[1086,40697,40698],{"class":1436},"      f",[1086,40700,861],{"class":1146},[1086,40702,39512],{"class":1105},[1086,40704,1398],{"class":4109},[1086,40706,1139],{"class":1436},[1086,40708,1410],{"class":4109},[1086,40710,33466],{"class":1146},[1086,40712,40713,40715,40717,40719,40721],{"class":1088,"line":20077},[1086,40714,40698],{"class":1436},[1086,40716,861],{"class":1146},[1086,40718,39529],{"class":1105},[1086,40720,2516],{"class":4109},[1086,40722,33466],{"class":1146},[1086,40724,40725],{"class":1088,"line":20083},[1086,40726,1279],{"class":1146},[1086,40728,40729],{"class":1088,"line":20095},[1086,40730,3390],{"emptyLinePlaceholder":738},[1086,40732,40733,40735,40737,40739,40741,40743],{"class":1088,"line":20112},[1086,40734,4797],{"class":1146},[1086,40736,39076],{"class":1423},[1086,40738,5979],{"class":4109},[1086,40740,39081],{"class":1436},[1086,40742,2778],{"class":4109},[1086,40744,1147],{"class":1146},[1086,40746,40747,40749,40751,40753,40755,40757,40759,40761,40763,40765,40767,40769,40771,40773,40775,40777,40779],{"class":1088,"line":20117},[1086,40748,39046],{"class":1105},[1086,40750,1398],{"class":4109},[1086,40752,1159],{"class":1146},[1086,40754,39096],{"class":1096},[1086,40756,1159],{"class":1146},[1086,40758,1227],{"class":1146},[1086,40760,39103],{"class":1436},[1086,40762,861],{"class":1146},[1086,40764,39108],{"class":1105},[1086,40766,2516],{"class":4109},[1086,40768,1227],{"class":1146},[1086,40770,1195],{"class":1146},[1086,40772,39056],{"class":1436},[1086,40774,39059],{"class":1096},[1086,40776,1159],{"class":1146},[1086,40778,1410],{"class":4109},[1086,40780,33466],{"class":1146},[1086,40782,40783],{"class":1088,"line":20139},[1086,40784,1285],{"class":1146},[1086,40786,40787],{"class":1088,"line":20169},[1086,40788,1291],{"class":1146},[863,40790,18681],{"id":18680},[842,40792,40793],{},"With about 50 lines of JavaScript and a simple Max for Live patcher, we've built a bridge between Ableton Live's arrangement markers and the wider world of data-driven music tools.",[842,40795,40796,40797,40800],{},"This approach demonstrates a key principle: ",[996,40798,40799],{},"start with the simplest thing that works",". A button, a script, and a text display. No complex UI, no elaborate state management. Just structured data flowing from your DAW to wherever it needs to go.",[842,40802,40803],{},"The JSON format we've chosen is intentionally minimal - project name, BPM, and an array of labeled timestamps. This makes it trivial to parse in any language and integrate with any system.",[1680,40805,40806],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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}",{"title":728,"searchDepth":729,"depth":729,"links":40808},[40809,40810,40811,40812,40817,40821,40827,40828,40833,40834,40835,40836,40837],{"id":37745,"depth":729,"text":37746},{"id":15347,"depth":729,"text":15348},{"id":18899,"depth":729,"text":38043},{"id":38075,"depth":729,"text":38076,"children":40813},[40814,40815,40816],{"id":38097,"depth":1112,"text":38098},{"id":38121,"depth":1112,"text":38122},{"id":38189,"depth":1112,"text":38190},{"id":38224,"depth":729,"text":38225,"children":40818},[40819,40820],{"id":39135,"depth":1112,"text":39136},{"id":39185,"depth":1112,"text":39186},{"id":39204,"depth":729,"text":39205,"children":40822},[40823,40824,40825,40826],{"id":39208,"depth":1112,"text":39209},{"id":39244,"depth":1112,"text":39245},{"id":39299,"depth":1112,"text":39300},{"id":39345,"depth":1112,"text":39346},{"id":39410,"depth":729,"text":39411},{"id":39634,"depth":729,"text":39635,"children":40829},[40830,40831,40832],{"id":39638,"depth":1112,"text":39639},{"id":39668,"depth":1112,"text":39669},{"id":39694,"depth":1112,"text":39695},{"id":39709,"depth":729,"text":39710},{"id":39756,"depth":729,"text":39757},{"id":16446,"depth":729,"text":31693},{"id":39859,"depth":729,"text":39860},{"id":18680,"depth":729,"text":18681},"2026-01-24T00:00:00.000Z","Build a Max for Live device that exports arrangement locators and BPM to JSON, ready for external tools and AI music analysis pipelines.",{"src":40841},"/images/blog/musictechlab_blog_ableton-locators-json-hero.webp",{"enabled":738,"items":40843},[40844,40846,40848,40850],{"text":40845,"icon":9547},"A one-click Max for Live device exports arrangement locators and BPM to JSON.",{"text":40847,"icon":11614},"Output feeds into AI models, cross-DAW workflows, and video editing tools.",{"text":40849,"icon":5365},"Built with three components: a button, JavaScript using LiveAPI, and a text display.",{"text":40851,"icon":40852},"Requires Ableton Live 12 Suite for Max for Live support.","i-lucide-headphones",{},{"title":458,"description":40839},[26062,18784],"znDSVZTcrgfgHaCE3fnvM0ylRVU8K_vjHV1ACi7Y0WY",{"id":40858,"title":711,"authors":40859,"badge":723,"body":40863,"category":42982,"client":723,"date":42983,"description":42984,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":42985,"keyTakeaways":42987,"meta":42999,"navigation":738,"path":712,"seo":43000,"status":723,"stem":713,"tags":43001,"teaser":723,"__hash__":43002},"posts/blog/sportstech/how-to-create-watch-face-app-for-garmin-watch.md",[40860],{"name":12840,"to":12838,"avatar":40861},{"src":40862},"/images/logo.svg",{"type":725,"value":40864,"toc":42937},[40865,40868,40870,40873,40882,40889,40927,40933,40935,40939,40942,40960,40963,40966,40968,40972,40975,40978,41010,41012,41014,41017,41053,41062,41064,41068,41072,41101,41105,41114,41162,41166,41169,41244,41252,41254,41258,41261,41267,41269,41273,41280,41284,41471,41475,41523,41525,41529,41534,41621,41623,41627,41633,41879,41882,41905,41907,41911,41917,42085,42098,42100,42104,42108,42123,42127,42193,42195,42199,42202,42242,42246,42272,42274,42278,42282,42304,42330,42335,42339,42414,42423,42425,42429,42432,42557,42561,42641,42643,42647,42651,42680,42683,42700,42704,42707,42724,42728,42762,42764,42768,42824,42826,42828,42831,42838,42842,42845,42871,42874,42878,42895,42897,42899,42934],[842,40866,40867],{},"hidden: true",[863,40869,27940],{"id":27939},[842,40871,40872],{},"Smartwatches have become essential tools for athletes and fitness enthusiasts. With thousands of apps available on platforms like Garmin's Connect IQ Store, there's also a huge opportunity for brands to extend their presence to users' wrists - even with something as simple as a custom watch face.",[842,40874,40875,40876,40881],{},"At MusicTech Lab, while working on software for ",[846,40877,40880],{"href":40878,"rel":40879},"https://www.beatbuddypro.com",[850],"BeatBuddy Pro"," - a programmable tempo trainer designed for swimmers to maintain consistent stroke rhythm and improve technique - we saw an opportunity to experiment with Garmin's Connect IQ platform. The result? A branded watch face dedicated to swimmers that displays weekly swimming distance alongside the standard time and date. Every glance at the watch shows how many kilometers you've swum in the last 7 days - a subtle motivator that reinforces training consistency while keeping the BeatBuddy brand visible.",[842,40883,40884,40885,40888],{},"In this guide, we'll share everything we learned building the ",[996,40886,40887],{},"BeatBuddy Watch Face"," for Garmin devices. Whether you're creating a watch face for your sports tech brand, a client, or just exploring the platform, this tutorial covers the complete journey from SDK setup to publishing on the Connect IQ Store.",[1045,40890,31345,40892,31345,40905,31345,40916],{"style":40891},"display: flex; justify-content: space-evenly; align-items: flex-end; gap: 10px; flex-wrap: wrap; width: 100%;",[1045,40893,38084,40895,38084,40900,31345],{"style":40894},"text-align: center; flex: 1;",[1027,40896],{"src":40897,"alt":40898,"style":40899},"/images/blog/garmin-watchface-simulator-fenix.png","BeatBuddy Pro Watch Face on Garmin Fenix 7","max-width: 100%; width: 280px;",[842,40901,40902],{},[964,40903,40904],{},"Fenix 7",[1045,40906,38084,40907,38084,40912,31345],{"style":40894},[1027,40908],{"src":40909,"alt":40910,"style":40911},"/images/blog/garmin-watchface-beatbuddy-device.png","BeatBuddy Pro Device","max-width: 100%; width: 220px;",[842,40913,40914],{},[964,40915,40880],{},[1045,40917,38084,40918,38084,40922,31345],{"style":40894},[1027,40919],{"src":40920,"alt":40921,"style":40899},"/images/blog/garmin-watchface-simulator-venu.png","BeatBuddy Pro Watch Face on Garmin Venu 2",[842,40923,40924],{},[964,40925,40926],{},"Venu 2",[842,40928,40930],{"style":40929},"text-align: center;",[964,40931,40932],{},"The BeatBuddy Pro Watch Face running in the Garmin simulator - with the BeatBuddy Pro tempo trainer in the center",[4937,40934],{},[863,40936,40938],{"id":40937},"where-sportstech-meets-musictech","Where SportsTech Meets MusicTech",[842,40940,40941],{},"The Garmin Connect IQ Store is a fascinating intersection of sports and music technology. Look at the top music apps:",[958,40943,40944,40949,40955],{},[961,40945,40946,40948],{},[996,40947,5070],{}," - 10M+ downloads, featuring a runner sprinting on the banner",[961,40950,40951,40954],{},[996,40952,40953],{},"Deezer"," - 5M+ downloads, showcasing a runner mid-stride",[961,40956,40957,40959],{},[996,40958,5059],{}," - 1M+ downloads, with an athlete in motion",[842,40961,40962],{},"Notice a pattern? Every major music streaming service markets their Garmin app with athletes in action. Music and sports are deeply intertwined - runners rely on playlists for motivation, cyclists sync their cadence to BPM, and fitness enthusiasts use audio cues throughout their workouts.",[842,40964,40965],{},"This is exactly why Garmin is such an interesting platform for us at MusicTech Lab. When you're building products at the intersection of music and fitness - like BeatBuddy's rhythm-based training tools - Garmin gives you direct access to users who already understand the connection between sound and performance.",[4937,40967],{},[863,40969,40971],{"id":40970},"why-garmin","Why Garmin?",[842,40973,40974],{},"Garmin dominates the serious sports watch market. Runners, cyclists, triathletes, and outdoor athletes overwhelmingly choose Garmin for its GPS accuracy, training features, and legendary battery life. This makes it the perfect platform for sports tech brands looking to reach active users.",[842,40976,40977],{},"The Connect IQ platform offers:",[958,40979,40980,40986,40992,40998,41004],{},[961,40981,40982,40985],{},[996,40983,40984],{},"Wide Device Support"," - One codebase runs on 40+ watch models (Forerunner, Fenix, Venu, Epix series)",[961,40987,40988,40991],{},[996,40989,40990],{},"Developer-Friendly SDK"," - Well-documented APIs for time, sensors, and graphics",[961,40993,40994,40997],{},[996,40995,40996],{},"Active Marketplace"," - The Connect IQ Store has millions of downloads",[961,40999,41000,41003],{},[996,41001,41002],{},"Target Audience"," - Direct access to serious athletes who actually use their watches daily",[961,41005,41006,41009],{},[996,41007,41008],{},"Battery Life"," - Garmin watches last weeks, not hours - your watch face gets seen constantly",[4937,41011],{},[863,41013,15348],{"id":15347},[842,41015,41016],{},"Before you begin, make sure you have:",[991,41018,41019,41030,41041,41047],{},[961,41020,41021,41024,41025],{},[996,41022,41023],{},"Garmin Connect IQ SDK"," - ",[846,41026,41029],{"href":41027,"rel":41028},"https://developer.garmin.com/connect-iq/sdk/",[850],"Download here",[961,41031,41032,41035,41036],{},[996,41033,41034],{},"Visual Studio Code"," - With the ",[846,41037,41040],{"href":41038,"rel":41039},"https://marketplace.visualstudio.com/items?itemName=garmin.monkey-c",[850],"Monkey C extension",[961,41042,41043,41046],{},[996,41044,41045],{},"A Garmin Watch"," (optional for testing) - The SDK includes a simulator",[961,41048,41049,41052],{},[996,41050,41051],{},"Developer Key"," - Generated during SDK setup",[41054,41055,41056],"blockquote",{},[842,41057,41058,41061],{},[996,41059,41060],{},"Note",": The Connect IQ SDK runs on macOS, Windows, and Linux. We used macOS for this tutorial.",[4937,41063],{},[863,41065,41067],{"id":41066},"step-1-install-the-connect-iq-sdk","Step 1: Install the Connect IQ SDK",[1074,41069,41071],{"id":41070},"download-and-extract","Download and Extract",[1013,41073,41075],{"className":1080,"code":41074,"language":1082,"meta":728,"style":728},"# Download from Garmin's developer portal\n# https://developer.garmin.com/connect-iq/sdk/\n\n# Extract to your preferred location (we use ~/Library/Application Support/Garmin/)\n# The SDK includes compiler, simulator, and device definitions\n",[895,41076,41077,41082,41087,41091,41096],{"__ignoreMap":728},[1086,41078,41079],{"class":1088,"line":1089},[1086,41080,41081],{"class":1427},"# Download from Garmin's developer portal\n",[1086,41083,41084],{"class":1088,"line":729},[1086,41085,41086],{"class":1427},"# https://developer.garmin.com/connect-iq/sdk/\n",[1086,41088,41089],{"class":1088,"line":1112},[1086,41090,3390],{"emptyLinePlaceholder":738},[1086,41092,41093],{"class":1088,"line":1181},[1086,41094,41095],{"class":1427},"# Extract to your preferred location (we use ~/Library/Application Support/Garmin/)\n",[1086,41097,41098],{"class":1088,"line":1205},[1086,41099,41100],{"class":1427},"# The SDK includes compiler, simulator, and device definitions\n",[1074,41102,41104],{"id":41103},"set-environment-variables","Set Environment Variables",[842,41106,41107,41108,28366,41111,1133],{},"Add to your ",[895,41109,41110],{},"~/.zshrc",[895,41112,41113],{},"~/.bashrc",[1013,41115,41117],{"className":1080,"code":41116,"language":1082,"meta":728,"style":728},"export GARMIN_HOME=\"$HOME/Library/Application Support/Garmin/ConnectIQ/Sdks/connectiq-sdk-mac-8.4.0-2025-12-03-5122605dc\"\nexport PATH=\"$PATH:$GARMIN_HOME/bin\"\n",[895,41118,41119,41138],{"__ignoreMap":728},[1086,41120,41121,41123,41126,41128,41130,41133,41136],{"class":1088,"line":1089},[1086,41122,3625],{"class":1155},[1086,41124,41125],{"class":1436}," GARMIN_HOME",[1086,41127,1440],{"class":1146},[1086,41129,1159],{"class":1146},[1086,41131,41132],{"class":1436},"$HOME",[1086,41134,41135],{"class":1096},"/Library/Application Support/Garmin/ConnectIQ/Sdks/connectiq-sdk-mac-8.4.0-2025-12-03-5122605dc",[1086,41137,4441],{"class":1146},[1086,41139,41140,41142,41145,41147,41149,41152,41154,41157,41160],{"class":1088,"line":729},[1086,41141,3625],{"class":1155},[1086,41143,41144],{"class":1436}," PATH",[1086,41146,1440],{"class":1146},[1086,41148,1159],{"class":1146},[1086,41150,41151],{"class":1436},"$PATH",[1086,41153,1133],{"class":1096},[1086,41155,41156],{"class":1436},"$GARMIN_HOME",[1086,41158,41159],{"class":1096},"/bin",[1086,41161,4441],{"class":1146},[1074,41163,41165],{"id":41164},"generate-developer-key","Generate Developer Key",[842,41167,41168],{},"You'll need a developer key to sign your apps:",[1013,41170,41172],{"className":1080,"code":41171,"language":1082,"meta":728,"style":728},"# Generate RSA key pair\nopenssl genrsa -out developer_key.pem 4096\n\n# Convert to DER format (required by Connect IQ)\nopenssl pkcs8 -topk8 -inform PEM -outform DER \\\n  -in developer_key.pem -out developer_key.der -nocrypt\n",[895,41173,41174,41179,41196,41200,41205,41229],{"__ignoreMap":728},[1086,41175,41176],{"class":1088,"line":1089},[1086,41177,41178],{"class":1427},"# Generate RSA key pair\n",[1086,41180,41181,41184,41187,41190,41193],{"class":1088,"line":729},[1086,41182,41183],{"class":1092},"openssl",[1086,41185,41186],{"class":1096}," genrsa",[1086,41188,41189],{"class":1096}," -out",[1086,41191,41192],{"class":1096}," developer_key.pem",[1086,41194,41195],{"class":1187}," 4096\n",[1086,41197,41198],{"class":1088,"line":1112},[1086,41199,3390],{"emptyLinePlaceholder":738},[1086,41201,41202],{"class":1088,"line":1181},[1086,41203,41204],{"class":1427},"# Convert to DER format (required by Connect IQ)\n",[1086,41206,41207,41209,41212,41215,41218,41221,41224,41227],{"class":1088,"line":1205},[1086,41208,41183],{"class":1092},[1086,41210,41211],{"class":1096}," pkcs8",[1086,41213,41214],{"class":1096}," -topk8",[1086,41216,41217],{"class":1096}," -inform",[1086,41219,41220],{"class":1096}," PEM",[1086,41222,41223],{"class":1096}," -outform",[1086,41225,41226],{"class":1096}," DER",[1086,41228,33060],{"class":1436},[1086,41230,41231,41234,41236,41238,41241],{"class":1088,"line":1276},[1086,41232,41233],{"class":1096},"  -in",[1086,41235,41192],{"class":1096},[1086,41237,41189],{"class":1096},[1086,41239,41240],{"class":1096}," developer_key.der",[1086,41242,41243],{"class":1096}," -nocrypt\n",[41054,41245,41246],{},[842,41247,41248,41251],{},[996,41249,41250],{},"Important",": Keep your developer key safe! You'll need the same key for all future updates to your app.",[4937,41253],{},[863,41255,41257],{"id":41256},"step-2-project-structure","Step 2: Project Structure",[842,41259,41260],{},"A typical Garmin watch face project follows this structure:",[1013,41262,41265],{"className":41263,"code":41264,"language":1018},[1016],"my-watchface/\n├── manifest.xml                    # App configuration & device support\n├── monkey.jungle                   # Build configuration\n├── keys/\n│   ├── developer_key.pem          # Private key (don't commit!)\n│   └── developer_key.der          # Signing key\n├── source/\n│   ├── MyWatchFaceApp.mc          # Application entry point\n│   └── MyWatchFaceView.mc         # Rendering logic\n└── resources/\n    ├── drawables/\n    │   ├── drawables.xml          # Drawable definitions\n    │   └── launcher_icon.png      # App icon (40x40)\n    ├── layouts/watchface.xml      # UI layout\n    └── strings/strings.xml        # Localized strings\n",[895,41266,41264],{"__ignoreMap":728},[4937,41268],{},[863,41270,41272],{"id":41271},"step-3-understanding-monkey-c","Step 3: Understanding Monkey C",[842,41274,41275,41276,41279],{},"Garmin uses ",[996,41277,41278],{},"Monkey C",", a custom language that resembles a mix of Java and JavaScript. Here's what makes it unique:",[1074,41281,41283],{"id":41282},"key-concepts","Key Concepts",[1013,41285,41287],{"className":33433,"code":41286,"language":33435,"meta":728,"style":728},"// Imports use the Toybox namespace\nimport Toybox.Graphics;\nimport Toybox.System;\nimport Toybox.WatchUi;\n\n// Classes extend built-in base classes\nclass MyWatchFace extends WatchUi.WatchFace {\n\n    // Type annotations are optional but recommended\n    private var _centerX as Number = 0;\n\n    // Constructor\n    function initialize() {\n        WatchFace.initialize();\n    }\n\n    // Called every minute (or second in high-power mode)\n    function onUpdate(dc as Dc) as Void {\n        // dc is the drawing context\n        dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);\n        dc.clear();\n    }\n}\n",[895,41288,41289,41294,41301,41306,41311,41315,41320,41327,41331,41336,41355,41359,41364,41375,41388,41392,41396,41401,41414,41419,41452,41463,41467],{"__ignoreMap":728},[1086,41290,41291],{"class":1088,"line":1089},[1086,41292,41293],{"class":1427},"// Imports use the Toybox namespace\n",[1086,41295,41296,41298],{"class":1088,"line":729},[1086,41297,6503],{"class":1423},[1086,41299,41300],{"class":1436}," Toybox.Graphics;\n",[1086,41302,41303],{"class":1088,"line":1112},[1086,41304,41305],{"class":1436},"import Toybox.System;\n",[1086,41307,41308],{"class":1088,"line":1181},[1086,41309,41310],{"class":1436},"import Toybox.WatchUi;\n",[1086,41312,41313],{"class":1088,"line":1205},[1086,41314,3390],{"emptyLinePlaceholder":738},[1086,41316,41317],{"class":1088,"line":1276},[1086,41318,41319],{"class":1427},"// Classes extend built-in base classes\n",[1086,41321,41322,41325],{"class":1088,"line":1282},[1086,41323,41324],{"class":1436},"class MyWatchFace extends WatchUi.WatchFace ",[1086,41326,1147],{"class":1146},[1086,41328,41329],{"class":1088,"line":1288},[1086,41330,3390],{"emptyLinePlaceholder":738},[1086,41332,41333],{"class":1088,"line":2685},[1086,41334,41335],{"class":1427},"    // Type annotations are optional but recommended\n",[1086,41337,41338,41341,41344,41347,41349,41352],{"class":1088,"line":2700},[1086,41339,41340],{"class":1436},"    private",[1086,41342,41343],{"class":1436}," var",[1086,41345,41346],{"class":1436}," _centerX",[1086,41348,19873],{"class":1423},[1086,41350,41351],{"class":1436}," Number",[1086,41353,41354],{"class":4109}," = 0;\n",[1086,41356,41357],{"class":1088,"line":3398},[1086,41358,3390],{"emptyLinePlaceholder":738},[1086,41360,41361],{"class":1088,"line":1715},[1086,41362,41363],{"class":1427},"    // Constructor\n",[1086,41365,41366,41369,41372],{"class":1088,"line":3409},[1086,41367,41368],{"class":1436},"    function",[1086,41370,41371],{"class":1436}," initialize",[1086,41373,41374],{"class":4109},"() {\n",[1086,41376,41377,41380,41382,41385],{"class":1088,"line":3415},[1086,41378,41379],{"class":1436},"        WatchFace",[1086,41381,861],{"class":4109},[1086,41383,41384],{"class":1436},"initialize",[1086,41386,41387],{"class":4109},"();\n",[1086,41389,41390],{"class":1088,"line":3421},[1086,41391,1279],{"class":1146},[1086,41393,41394],{"class":1088,"line":3427},[1086,41395,3390],{"emptyLinePlaceholder":738},[1086,41397,41398],{"class":1088,"line":3433},[1086,41399,41400],{"class":1427},"    // Called every minute (or second in high-power mode)\n",[1086,41402,41403,41406,41409,41412],{"class":1088,"line":3439},[1086,41404,41405],{"class":1436},"    function onUpdate(dc ",[1086,41407,41408],{"class":1423},"as",[1086,41410,41411],{"class":1436}," Dc) as Void ",[1086,41413,1147],{"class":1146},[1086,41415,41416],{"class":1088,"line":3444},[1086,41417,41418],{"class":1427},"        // dc is the drawing context\n",[1086,41420,41421,41424,41426,41429,41431,41434,41436,41439,41441,41444,41446,41449],{"class":1088,"line":3450},[1086,41422,41423],{"class":1436},"        dc",[1086,41425,861],{"class":4109},[1086,41427,41428],{"class":1436},"setColor",[1086,41430,1398],{"class":4109},[1086,41432,41433],{"class":1436},"Graphics",[1086,41435,861],{"class":4109},[1086,41437,41438],{"class":1436},"COLOR_WHITE",[1086,41440,1227],{"class":1146},[1086,41442,41443],{"class":1436}," Graphics",[1086,41445,861],{"class":4109},[1086,41447,41448],{"class":1436},"COLOR_BLACK",[1086,41450,41451],{"class":4109},");\n",[1086,41453,41454,41456,41458,41461],{"class":1088,"line":3456},[1086,41455,41423],{"class":1436},[1086,41457,861],{"class":4109},[1086,41459,41460],{"class":1436},"clear",[1086,41462,41387],{"class":4109},[1086,41464,41465],{"class":1088,"line":3462},[1086,41466,1279],{"class":1146},[1086,41468,41469],{"class":1088,"line":3467},[1086,41470,1291],{"class":1436},[1074,41472,41474],{"id":41473},"important-differences-from-javascriptjava","Important Differences from JavaScript/Java",[871,41476,41477,41485],{},[874,41478,41479],{},[877,41480,41481,41483],{},[880,41482,1974],{},[880,41484,41278],{},[887,41486,41487,41495,41503,41511],{},[877,41488,41489,41492],{},[892,41490,41491],{},"Type System",[892,41493,41494],{},"Optional type annotations, runtime type checking",[877,41496,41497,41500],{},[892,41498,41499],{},"Memory",[892,41501,41502],{},"Limited heap (varies by device, typically 28-128KB)",[877,41504,41505,41508],{},[892,41506,41507],{},"Floats",[892,41509,41510],{},"Use sparingly - they consume more memory",[877,41512,41513,41516],{},[892,41514,41515],{},"Strings",[892,41517,41518,41519,41522],{},"Immutable, use ",[895,41520,41521],{},"Lang.format()"," for concatenation",[4937,41524],{},[863,41526,41528],{"id":41527},"step-4-the-application-entry-point","Step 4: The Application Entry Point",[842,41530,15434,41531,1133],{},[895,41532,41533],{},"source/BeatBuddyWatchFaceApp.mc",[1013,41535,41537],{"className":33433,"code":41536,"language":33435,"meta":728,"style":728},"class BeatBuddyWatchFaceApp extends Application.AppBase {\n    function initialize() { AppBase.initialize(); }\n\n    function getInitialView() {\n        return [new BeatBuddyWatchFaceView()];\n    }\n}\n",[895,41538,41539,41559,41582,41586,41597,41613,41617],{"__ignoreMap":728},[1086,41540,41541,41543,41546,41549,41552,41554,41557],{"class":1088,"line":1089},[1086,41542,4036],{"class":1155},[1086,41544,41545],{"class":1092}," BeatBuddyWatchFaceApp",[1086,41547,41548],{"class":1155}," extends",[1086,41550,41551],{"class":1092}," Application",[1086,41553,861],{"class":1146},[1086,41555,41556],{"class":1092},"AppBase",[1086,41558,1164],{"class":1146},[1086,41560,41561,41563,41565,41567,41569,41572,41574,41576,41578,41580],{"class":1088,"line":729},[1086,41562,41368],{"class":1155},[1086,41564,41371],{"class":1105},[1086,41566,2516],{"class":1146},[1086,41568,4520],{"class":1146},[1086,41570,41571],{"class":1436}," AppBase",[1086,41573,861],{"class":1146},[1086,41575,41384],{"class":1105},[1086,41577,2516],{"class":4109},[1086,41579,4639],{"class":1146},[1086,41581,27132],{"class":1146},[1086,41583,41584],{"class":1088,"line":1112},[1086,41585,3390],{"emptyLinePlaceholder":738},[1086,41587,41588,41590,41593,41595],{"class":1088,"line":1181},[1086,41589,41368],{"class":1155},[1086,41591,41592],{"class":1105}," getInitialView",[1086,41594,2516],{"class":1146},[1086,41596,1164],{"class":1146},[1086,41598,41599,41601,41603,41605,41608,41611],{"class":1088,"line":1205},[1086,41600,4239],{"class":1423},[1086,41602,1217],{"class":4109},[1086,41604,24153],{"class":1146},[1086,41606,41607],{"class":1105}," BeatBuddyWatchFaceView",[1086,41609,41610],{"class":4109},"()]",[1086,41612,33466],{"class":1146},[1086,41614,41615],{"class":1088,"line":1276},[1086,41616,1279],{"class":1146},[1086,41618,41619],{"class":1088,"line":1282},[1086,41620,1291],{"class":1146},[4937,41622],{},[863,41624,41626],{"id":41625},"step-5-the-watch-face-view","Step 5: The Watch Face View",[842,41628,41629,41630,1133],{},"This is where the magic happens. Create ",[895,41631,41632],{},"source/BeatBuddyWatchFaceView.mc",[1013,41634,41636],{"className":33433,"code":41635,"language":33435,"meta":728,"style":728},"class BeatBuddyWatchFaceView extends WatchUi.WatchFace {\n    private const COLOR_PRIMARY = 0xFFFF00;  // Yellow\n\n    function onUpdate(dc) {\n        dc.setColor(0x000000, 0x000000);\n        dc.clear();\n\n        // Draw time\n        var time = System.getClockTime();\n        var timeStr = Lang.format(\"$1$:$2$\", [time.hour, time.min.format(\"%02d\")]);\n        dc.drawText(_centerX, _centerY, Graphics.FONT_NUMBER_THAI_HOT, timeStr, ...);\n\n        // Draw date, battery, branding...\n    }\n}\n",[895,41637,41638,41656,41676,41680,41696,41718,41730,41734,41739,41759,41822,41862,41866,41871,41875],{"__ignoreMap":728},[1086,41639,41640,41642,41644,41646,41649,41651,41654],{"class":1088,"line":1089},[1086,41641,4036],{"class":1155},[1086,41643,41607],{"class":1092},[1086,41645,41548],{"class":1155},[1086,41647,41648],{"class":1092}," WatchUi",[1086,41650,861],{"class":1146},[1086,41652,41653],{"class":1092},"WatchFace",[1086,41655,1164],{"class":1146},[1086,41657,41658,41660,41663,41666,41668,41671,41673],{"class":1088,"line":729},[1086,41659,41340],{"class":1155},[1086,41661,41662],{"class":1436}," const ",[1086,41664,41665],{"class":4109},"COLOR_PRIMARY",[1086,41667,19552],{"class":1146},[1086,41669,41670],{"class":1187}," 0xFFFF00",[1086,41672,4639],{"class":1146},[1086,41674,41675],{"class":1427},"  // Yellow\n",[1086,41677,41678],{"class":1088,"line":1112},[1086,41679,3390],{"emptyLinePlaceholder":738},[1086,41681,41682,41684,41687,41689,41692,41694],{"class":1088,"line":1181},[1086,41683,41368],{"class":1155},[1086,41685,41686],{"class":1105}," onUpdate",[1086,41688,1398],{"class":1146},[1086,41690,41691],{"class":1401},"dc",[1086,41693,1410],{"class":1146},[1086,41695,1164],{"class":1146},[1086,41697,41698,41700,41702,41704,41706,41709,41711,41714,41716],{"class":1088,"line":1205},[1086,41699,41423],{"class":1436},[1086,41701,861],{"class":1146},[1086,41703,41428],{"class":1105},[1086,41705,1398],{"class":4109},[1086,41707,41708],{"class":1187},"0x000000",[1086,41710,1227],{"class":1146},[1086,41712,41713],{"class":1187}," 0x000000",[1086,41715,1410],{"class":4109},[1086,41717,33466],{"class":1146},[1086,41719,41720,41722,41724,41726,41728],{"class":1088,"line":1276},[1086,41721,41423],{"class":1436},[1086,41723,861],{"class":1146},[1086,41725,41460],{"class":1105},[1086,41727,2516],{"class":4109},[1086,41729,33466],{"class":1146},[1086,41731,41732],{"class":1088,"line":1282},[1086,41733,3390],{"emptyLinePlaceholder":738},[1086,41735,41736],{"class":1088,"line":1288},[1086,41737,41738],{"class":1427},"        // Draw time\n",[1086,41740,41741,41743,41745,41747,41750,41752,41755,41757],{"class":1088,"line":2685},[1086,41742,38724],{"class":1155},[1086,41744,38787],{"class":1436},[1086,41746,19552],{"class":1146},[1086,41748,41749],{"class":1436}," System",[1086,41751,861],{"class":1146},[1086,41753,41754],{"class":1105},"getClockTime",[1086,41756,2516],{"class":4109},[1086,41758,33466],{"class":1146},[1086,41760,41761,41763,41766,41768,41771,41773,41775,41777,41779,41782,41784,41786,41788,41790,41792,41795,41797,41799,41801,41804,41806,41808,41810,41812,41815,41817,41820],{"class":1088,"line":2700},[1086,41762,38724],{"class":1155},[1086,41764,41765],{"class":1436}," timeStr",[1086,41767,19552],{"class":1146},[1086,41769,41770],{"class":1436}," Lang",[1086,41772,861],{"class":1146},[1086,41774,8973],{"class":1105},[1086,41776,1398],{"class":4109},[1086,41778,1159],{"class":1146},[1086,41780,41781],{"class":1096},"$1$:$2$",[1086,41783,1159],{"class":1146},[1086,41785,1227],{"class":1146},[1086,41787,1217],{"class":4109},[1086,41789,31109],{"class":1436},[1086,41791,861],{"class":1146},[1086,41793,41794],{"class":1436},"hour",[1086,41796,1227],{"class":1146},[1086,41798,38787],{"class":1436},[1086,41800,861],{"class":1146},[1086,41802,41803],{"class":1436},"min",[1086,41805,861],{"class":1146},[1086,41807,8973],{"class":1105},[1086,41809,1398],{"class":4109},[1086,41811,1159],{"class":1146},[1086,41813,41814],{"class":1096},"%02d",[1086,41816,1159],{"class":1146},[1086,41818,41819],{"class":4109},")])",[1086,41821,33466],{"class":1146},[1086,41823,41824,41826,41828,41831,41833,41836,41838,41841,41843,41845,41847,41850,41852,41854,41856,41858,41860],{"class":1088,"line":3398},[1086,41825,41423],{"class":1436},[1086,41827,861],{"class":1146},[1086,41829,41830],{"class":1105},"drawText",[1086,41832,1398],{"class":4109},[1086,41834,41835],{"class":1436},"_centerX",[1086,41837,1227],{"class":1146},[1086,41839,41840],{"class":1436}," _centerY",[1086,41842,1227],{"class":1146},[1086,41844,41443],{"class":1436},[1086,41846,861],{"class":1146},[1086,41848,41849],{"class":1436},"FONT_NUMBER_THAI_HOT",[1086,41851,1227],{"class":1146},[1086,41853,41765],{"class":1436},[1086,41855,1227],{"class":1146},[1086,41857,31085],{"class":1146},[1086,41859,1410],{"class":4109},[1086,41861,33466],{"class":1146},[1086,41863,41864],{"class":1088,"line":1715},[1086,41865,3390],{"emptyLinePlaceholder":738},[1086,41867,41868],{"class":1088,"line":3409},[1086,41869,41870],{"class":1427},"        // Draw date, battery, branding...\n",[1086,41872,41873],{"class":1088,"line":3415},[1086,41874,1279],{"class":1146},[1086,41876,41877],{"class":1088,"line":3421},[1086,41878,1291],{"class":1146},[842,41880,41881],{},"Key methods to implement:",[958,41883,41884,41890,41896],{},[961,41885,41886,41889],{},[895,41887,41888],{},"onLayout(dc)"," - get screen dimensions",[961,41891,41892,41895],{},[895,41893,41894],{},"onUpdate(dc)"," - main render loop (called every minute)",[961,41897,41898,10501,41901,41904],{},[895,41899,41900],{},"onEnterSleep()",[895,41902,41903],{},"onExitSleep()"," - handle always-on display mode",[4937,41906],{},[863,41908,41910],{"id":41909},"step-6-configure-the-manifest","Step 6: Configure the Manifest",[842,41912,5119,41913,41916],{},[895,41914,41915],{},"manifest.xml"," defines your app's metadata and supported devices:",[1013,41918,41920],{"className":11154,"code":41919,"language":11157,"meta":728,"style":728},"\u003Ciq:application entry=\"BeatBuddyWatchFaceApp\" type=\"watchface\" version=\"1.0.1\">\n    \u003Ciq:products>\n        \u003Ciq:product id=\"venu3\"/>\n        \u003Ciq:product id=\"fenix7\"/>\n        \u003Ciq:product id=\"fr965\"/>\n        \u003C!-- Add more devices as needed -->\n    \u003C/iq:products>\n\u003C/iq:application>\n",[895,41921,41922,41971,41984,42010,42033,42056,42061,42073],{"__ignoreMap":728},[1086,41923,41924,41926,41929,41931,41934,41937,41939,41941,41944,41946,41949,41951,41953,41956,41958,41960,41962,41964,41967,41969],{"class":1088,"line":1089},[1086,41925,11164],{"class":1146},[1086,41927,41928],{"class":4109},"iq",[1086,41930,1133],{"class":1146},[1086,41932,41933],{"class":4109},"application",[1086,41935,41936],{"class":1155}," entry",[1086,41938,1440],{"class":1146},[1086,41940,1159],{"class":1146},[1086,41942,41943],{"class":1096},"BeatBuddyWatchFaceApp",[1086,41945,1159],{"class":1146},[1086,41947,41948],{"class":1155}," type",[1086,41950,1440],{"class":1146},[1086,41952,1159],{"class":1146},[1086,41954,41955],{"class":1096},"watchface",[1086,41957,1159],{"class":1146},[1086,41959,15610],{"class":1155},[1086,41961,1440],{"class":1146},[1086,41963,1159],{"class":1146},[1086,41965,41966],{"class":1096},"1.0.1",[1086,41968,1159],{"class":1146},[1086,41970,11170],{"class":1146},[1086,41972,41973,41975,41977,41979,41982],{"class":1088,"line":729},[1086,41974,11190],{"class":1146},[1086,41976,41928],{"class":4109},[1086,41978,1133],{"class":1146},[1086,41980,41981],{"class":4109},"products",[1086,41983,11170],{"class":1146},[1086,41985,41986,41988,41990,41992,41995,41998,42000,42002,42005,42007],{"class":1088,"line":1112},[1086,41987,18297],{"class":1146},[1086,41989,41928],{"class":4109},[1086,41991,1133],{"class":1146},[1086,41993,41994],{"class":4109},"product",[1086,41996,41997],{"class":1155}," id",[1086,41999,1440],{"class":1146},[1086,42001,1159],{"class":1146},[1086,42003,42004],{"class":1096},"venu3",[1086,42006,1159],{"class":1146},[1086,42008,42009],{"class":1146},"/>\n",[1086,42011,42012,42014,42016,42018,42020,42022,42024,42026,42029,42031],{"class":1088,"line":1181},[1086,42013,18297],{"class":1146},[1086,42015,41928],{"class":4109},[1086,42017,1133],{"class":1146},[1086,42019,41994],{"class":4109},[1086,42021,41997],{"class":1155},[1086,42023,1440],{"class":1146},[1086,42025,1159],{"class":1146},[1086,42027,42028],{"class":1096},"fenix7",[1086,42030,1159],{"class":1146},[1086,42032,42009],{"class":1146},[1086,42034,42035,42037,42039,42041,42043,42045,42047,42049,42052,42054],{"class":1088,"line":1205},[1086,42036,18297],{"class":1146},[1086,42038,41928],{"class":4109},[1086,42040,1133],{"class":1146},[1086,42042,41994],{"class":4109},[1086,42044,41997],{"class":1155},[1086,42046,1440],{"class":1146},[1086,42048,1159],{"class":1146},[1086,42050,42051],{"class":1096},"fr965",[1086,42053,1159],{"class":1146},[1086,42055,42009],{"class":1146},[1086,42057,42058],{"class":1088,"line":1276},[1086,42059,42060],{"class":1427},"        \u003C!-- Add more devices as needed -->\n",[1086,42062,42063,42065,42067,42069,42071],{"class":1088,"line":1282},[1086,42064,17914],{"class":1146},[1086,42066,41928],{"class":4109},[1086,42068,1133],{"class":1146},[1086,42070,41981],{"class":4109},[1086,42072,11170],{"class":1146},[1086,42074,42075,42077,42079,42081,42083],{"class":1088,"line":1288},[1086,42076,11201],{"class":1146},[1086,42078,41928],{"class":4109},[1086,42080,1133],{"class":1146},[1086,42082,41933],{"class":4109},[1086,42084,11170],{"class":1146},[41054,42086,42087],{},[842,42088,42089,42092,42093,861],{},[996,42090,42091],{},"Tip",": Find device IDs in the ",[846,42094,42097],{"href":42095,"rel":42096},"https://developer.garmin.com/connect-iq/compatible-devices/",[850],"Connect IQ Device Reference",[4937,42099],{},[863,42101,42103],{"id":42102},"step-7-build-and-test","Step 7: Build and Test",[1074,42105,42107],{"id":42106},"using-vs-code","Using VS Code",[991,42109,42110,42113,42120],{},[961,42111,42112],{},"Open your project folder",[961,42114,42115,42116,42119],{},"Press ",[895,42117,42118],{},"Cmd+Shift+P"," → \"Monkey C: Build Current Project\"",[961,42121,42122],{},"Choose your target device",[1074,42124,42126],{"id":42125},"using-command-line","Using Command Line",[1013,42128,42130],{"className":1080,"code":42129,"language":1082,"meta":728,"style":728},"# Build and run in simulator\nmonkeyc -f monkey.jungle -o bin/app.prg -d fenix7 -y keys/developer_key.der\nmonkeydo bin/app.prg fenix7\n\n# Or with a Makefile:\nmake run DEVICE=venu3\n",[895,42131,42132,42137,42164,42174,42178,42183],{"__ignoreMap":728},[1086,42133,42134],{"class":1088,"line":1089},[1086,42135,42136],{"class":1427},"# Build and run in simulator\n",[1086,42138,42139,42142,42144,42147,42149,42152,42155,42158,42161],{"class":1088,"line":729},[1086,42140,42141],{"class":1092},"monkeyc",[1086,42143,29789],{"class":1096},[1086,42145,42146],{"class":1096}," monkey.jungle",[1086,42148,31515],{"class":1096},[1086,42150,42151],{"class":1096}," bin/app.prg",[1086,42153,42154],{"class":1096}," -d",[1086,42156,42157],{"class":1096}," fenix7",[1086,42159,42160],{"class":1096}," -y",[1086,42162,42163],{"class":1096}," keys/developer_key.der\n",[1086,42165,42166,42169,42171],{"class":1088,"line":1112},[1086,42167,42168],{"class":1092},"monkeydo",[1086,42170,42151],{"class":1096},[1086,42172,42173],{"class":1096}," fenix7\n",[1086,42175,42176],{"class":1088,"line":1181},[1086,42177,3390],{"emptyLinePlaceholder":738},[1086,42179,42180],{"class":1088,"line":1205},[1086,42181,42182],{"class":1427},"# Or with a Makefile:\n",[1086,42184,42185,42188,42190],{"class":1088,"line":1276},[1086,42186,42187],{"class":1092},"make",[1086,42189,11970],{"class":1096},[1086,42191,42192],{"class":1096}," DEVICE=venu3\n",[4937,42194],{},[863,42196,42198],{"id":42197},"step-8-testing-in-the-simulator","Step 8: Testing in the Simulator",[842,42200,42201],{},"The Connect IQ Simulator provides a realistic testing environment:",[1013,42203,42205],{"className":1080,"code":42204,"language":1082,"meta":728,"style":728},"# Start the simulator\nmake simulator\n\n# Or manually\nopen \"$GARMIN_HOME/bin/ConnectIQ.app\"\n",[895,42206,42207,42212,42219,42223,42228],{"__ignoreMap":728},[1086,42208,42209],{"class":1088,"line":1089},[1086,42210,42211],{"class":1427},"# Start the simulator\n",[1086,42213,42214,42216],{"class":1088,"line":729},[1086,42215,42187],{"class":1092},[1086,42217,42218],{"class":1096}," simulator\n",[1086,42220,42221],{"class":1088,"line":1112},[1086,42222,3390],{"emptyLinePlaceholder":738},[1086,42224,42225],{"class":1088,"line":1181},[1086,42226,42227],{"class":1427},"# Or manually\n",[1086,42229,42230,42233,42235,42237,42240],{"class":1088,"line":1205},[1086,42231,42232],{"class":1092},"open",[1086,42234,1195],{"class":1146},[1086,42236,41156],{"class":1436},[1086,42238,42239],{"class":1096},"/bin/ConnectIQ.app",[1086,42241,4441],{"class":1146},[1074,42243,42245],{"id":42244},"simulator-features","Simulator Features",[958,42247,42248,42254,42260,42266],{},[961,42249,42250,42253],{},[996,42251,42252],{},"Time Control"," - Set any time to test rendering",[961,42255,42256,42259],{},[996,42257,42258],{},"Battery Simulation"," - Test low battery warnings",[961,42261,42262,42265],{},[996,42263,42264],{},"Sensor Data"," - Simulate heart rate, steps, GPS",[961,42267,42268,42271],{},[996,42269,42270],{},"Multiple Devices"," - Test on different screen sizes",[4937,42273],{},[863,42275,42277],{"id":42276},"step-9-deploy-to-your-watch","Step 9: Deploy to Your Watch",[1074,42279,42281],{"id":42280},"method-a-usb-fastest-for-testing","Method A: USB (Fastest for Testing)",[991,42283,42284,42287,42298,42301],{},[961,42285,42286],{},"Connect your watch via USB",[961,42288,42289,42290,42293,42294,42297],{},"Copy the ",[895,42291,42292],{},".prg"," file to ",[895,42295,42296],{},"GARMIN/APPS/"," on the watch",[961,42299,42300],{},"Safely eject the watch",[961,42302,42303],{},"On the watch: Settings → Watch Faces → Select your app",[1045,42305,31345,42307,31345,42319],{"style":42306},"display: flex; justify-content: center; align-items: flex-end; gap: 20px; flex-wrap: wrap;",[1045,42308,38084,42309,38084,42314,31345],{"style":40929},[1027,42310],{"src":42311,"alt":42312,"style":42313},"/images/blog/garmin-watchface-installed-real-watch.jpg","BeatBuddy Pro Watch Face installed on real Garmin watch","max-width: 280px;",[842,42315,42316],{},[964,42317,42318],{},"Installation confirmation",[1045,42320,38084,42321,38084,42325,31345],{"style":40929},[1027,42322],{"src":42323,"alt":42324,"style":42313},"/images/blog/garmin-watchface-real-watch-swimming-distance.webp","BeatBuddy Pro Watch Face showing 46.6 km weekly swimming distance",[842,42326,42327],{},[964,42328,42329],{},"Watch face in action - 46.6 km since last restart",[842,42331,42332],{"style":40929},[964,42333,42334],{},"The BeatBuddy Pro Watch Face installed on a real Garmin Fenix watch",[1074,42336,42338],{"id":42337},"method-b-connect-iq-store-production","Method B: Connect IQ Store (Production)",[991,42340,42341,42389,42397,42411],{},[961,42342,42343,42344,42347,42348,42385,42388],{},"Build the release ",[895,42345,42346],{},".iq"," package for all supported devices:",[1013,42349,42351],{"className":1080,"code":42350,"language":1082,"meta":728,"style":728},"make release\n# Building for all devices...\n# 0 OUT OF 67 DEVICES BUILT\n# 1 OUT OF 67 DEVICES BUILT\n# ...\n# 67 OUT OF 67 DEVICES BUILT\n",[895,42352,42353,42360,42365,42370,42375,42380],{"__ignoreMap":728},[1086,42354,42355,42357],{"class":1088,"line":1089},[1086,42356,42187],{"class":1092},[1086,42358,42359],{"class":1096}," release\n",[1086,42361,42362],{"class":1088,"line":729},[1086,42363,42364],{"class":1427},"# Building for all devices...\n",[1086,42366,42367],{"class":1088,"line":1112},[1086,42368,42369],{"class":1427},"# 0 OUT OF 67 DEVICES BUILT\n",[1086,42371,42372],{"class":1088,"line":1181},[1086,42373,42374],{"class":1427},"# 1 OUT OF 67 DEVICES BUILT\n",[1086,42376,42377],{"class":1088,"line":1205},[1086,42378,42379],{"class":1427},"# ...\n",[1086,42381,42382],{"class":1088,"line":1276},[1086,42383,42384],{"class":1427},"# 67 OUT OF 67 DEVICES BUILT\n",[42386,42387],"br",{},"The SDK automatically compiles your app for all 67 compatible devices in a single command.",[961,42390,42391,42392],{},"Create a developer account at ",[846,42393,42396],{"href":42394,"rel":42395},"https://apps.garmin.com/developer",[850],"apps.garmin.com/developer",[961,42398,42399,42400],{},"Upload your app with:",[958,42401,42402,42405,42408],{},[961,42403,42404],{},"Screenshots (454×454 for round faces)",[961,42406,42407],{},"Description and changelog",[961,42409,42410],{},"Privacy policy (required)",[961,42412,42413],{},"Submit for review (typically 1-3 days)",[842,42415,42416,42420],{},[1027,42417],{"alt":42418,"src":42419},"BeatBuddy Pro Watch Face on Connect IQ Store","/images/blog/garmin-watchface-connectiq-store.png",[964,42421,42422],{},"The BeatBuddy Pro Watch Face listing on the Garmin Connect IQ Store",[4937,42424],{},[863,42426,42428],{"id":42427},"accessing-fitness-data","Accessing Fitness Data",[842,42430,42431],{},"One of Garmin's strengths is access to fitness sensors. Here's how we display weekly swimming distance:",[1013,42433,42435],{"className":33433,"code":42434,"language":33435,"meta":728,"style":728},"// Get weekly distance from ActivityMonitor\nvar history = ActivityMonitor.getHistory();\nvar totalKm = 0.0f;\nfor (var i = 0; i \u003C 7; i++) {\n    totalKm += history[i].distance / 100000.0f;  // cm to km\n}\n",[895,42436,42437,42442,42463,42481,42515,42553],{"__ignoreMap":728},[1086,42438,42439],{"class":1088,"line":1089},[1086,42440,42441],{"class":1427},"// Get weekly distance from ActivityMonitor\n",[1086,42443,42444,42446,42449,42451,42454,42456,42459,42461],{"class":1088,"line":729},[1086,42445,38651],{"class":1155},[1086,42447,42448],{"class":1436}," history ",[1086,42450,1440],{"class":1146},[1086,42452,42453],{"class":1436}," ActivityMonitor",[1086,42455,861],{"class":1146},[1086,42457,42458],{"class":1105},"getHistory",[1086,42460,2516],{"class":1436},[1086,42462,33466],{"class":1146},[1086,42464,42465,42467,42470,42472,42474,42476,42479],{"class":1088,"line":1112},[1086,42466,38651],{"class":1155},[1086,42468,42469],{"class":1436}," totalKm ",[1086,42471,1440],{"class":1146},[1086,42473,22718],{"class":1436},[1086,42475,861],{"class":1146},[1086,42477,42478],{"class":1436},"0f",[1086,42480,33466],{"class":1146},[1086,42482,42483,42485,42487,42489,42492,42494,42496,42498,42500,42502,42505,42507,42509,42511,42513],{"class":1088,"line":1181},[1086,42484,10799],{"class":1423},[1086,42486,5979],{"class":1436},[1086,42488,38651],{"class":1155},[1086,42490,42491],{"class":1436}," i ",[1086,42493,1440],{"class":1146},[1086,42495,22718],{"class":1187},[1086,42497,4639],{"class":1146},[1086,42499,42491],{"class":1436},[1086,42501,11164],{"class":1146},[1086,42503,42504],{"class":1187}," 7",[1086,42506,4639],{"class":1146},[1086,42508,33757],{"class":1436},[1086,42510,38678],{"class":1146},[1086,42512,2778],{"class":1436},[1086,42514,1147],{"class":1146},[1086,42516,42517,42520,42523,42526,42528,42530,42532,42534,42537,42539,42542,42544,42546,42548,42550],{"class":1088,"line":1205},[1086,42518,42519],{"class":1436},"    totalKm",[1086,42521,42522],{"class":1146}," +=",[1086,42524,42525],{"class":1436}," history",[1086,42527,4340],{"class":4109},[1086,42529,33784],{"class":1436},[1086,42531,4420],{"class":4109},[1086,42533,861],{"class":1146},[1086,42535,42536],{"class":1436},"distance",[1086,42538,26689],{"class":1146},[1086,42540,42541],{"class":4109}," 100000",[1086,42543,861],{"class":1146},[1086,42545,4417],{"class":4109},[1086,42547,5962],{"class":1436},[1086,42549,4639],{"class":1146},[1086,42551,42552],{"class":1427},"  // cm to km\n",[1086,42554,42555],{"class":1088,"line":1276},[1086,42556,1291],{"class":1146},[1074,42558,42560],{"id":42559},"available-data-points","Available Data Points",[871,42562,42563,42574],{},[874,42564,42565],{},[877,42566,42567,42570,42572],{},[880,42568,42569],{},"Data",[880,42571,11843],{},[880,42573,2719],{},[887,42575,42576,42589,42602,42615,42628],{},[877,42577,42578,42581,42586],{},[892,42579,42580],{},"Heart Rate",[892,42582,42583],{},[895,42584,42585],{},"Activity.getActivityInfo().currentHeartRate",[892,42587,42588],{},"Real-time from sensor",[877,42590,42591,42594,42599],{},[892,42592,42593],{},"Steps",[892,42595,42596],{},[895,42597,42598],{},"ActivityMonitor.getInfo().steps",[892,42600,42601],{},"Today's count",[877,42603,42604,42607,42612],{},[892,42605,42606],{},"Distance",[892,42608,42609],{},[895,42610,42611],{},"ActivityMonitor.getHistory()",[892,42613,42614],{},"Historical data",[877,42616,42617,42620,42625],{},[892,42618,42619],{},"Calories",[892,42621,42622],{},[895,42623,42624],{},"ActivityMonitor.getInfo().calories",[892,42626,42627],{},"Daily estimate",[877,42629,42630,42633,42638],{},[892,42631,42632],{},"Battery",[892,42634,42635],{},[895,42636,42637],{},"System.getSystemStats().battery",[892,42639,42640],{},"Percentage",[4937,42642],{},[863,42644,42646],{"id":42645},"tips-and-best-practices","Tips and Best Practices",[1074,42648,42650],{"id":42649},"performance","Performance",[958,42652,42653,42662,42668,42674],{},[961,42654,42655,27714,42658,42661],{},[996,42656,42657],{},"Minimize memory allocation",[895,42659,42660],{},"onUpdate()"," - it's called frequently",[961,42663,42664,42667],{},[996,42665,42666],{},"Cache calculated values"," when possible",[961,42669,42670,42673],{},[996,42671,42672],{},"Avoid floating-point math"," unless necessary",[961,42675,42676,42679],{},[996,42677,42678],{},"Use built-in fonts"," instead of custom fonts when possible",[1074,42681,41008],{"id":42682},"battery-life",[958,42684,42685,42688,42694],{},[961,42686,42687],{},"Watch faces update once per minute by default",[961,42689,42690,42693],{},[996,42691,42692],{},"Don't request more frequent updates"," unless the user is actively looking at the watch",[961,42695,42696,42697,42699],{},"Implement ",[895,42698,41900],{}," to reduce rendering in always-on mode",[1074,42701,42703],{"id":42702},"screen-sizes","Screen Sizes",[842,42705,42706],{},"Garmin watches have various screen sizes (218×218 to 454×454). Always:",[958,42708,42709,42718,42721],{},[961,42710,42711,42712,1589,42715],{},"Get dimensions from ",[895,42713,42714],{},"dc.getWidth()",[895,42716,42717],{},"dc.getHeight()",[961,42719,42720],{},"Use relative positioning, not hard-coded coordinates",[961,42722,42723],{},"Test on multiple devices in the simulator",[1074,42725,42727],{"id":42726},"debugging","Debugging",[1013,42729,42731],{"className":33433,"code":42730,"language":33435,"meta":728,"style":728},"System.println(\"Debug: value = \" + value);  // Print to simulator console\n",[895,42732,42733],{"__ignoreMap":728},[1086,42734,42735,42738,42740,42743,42745,42747,42750,42752,42754,42757,42759],{"class":1088,"line":1089},[1086,42736,42737],{"class":1436},"System",[1086,42739,861],{"class":1146},[1086,42741,42742],{"class":1105},"println",[1086,42744,1398],{"class":1436},[1086,42746,1159],{"class":1146},[1086,42748,42749],{"class":1096},"Debug: value = ",[1086,42751,1159],{"class":1146},[1086,42753,33787],{"class":1146},[1086,42755,42756],{"class":1436}," value)",[1086,42758,4639],{"class":1146},[1086,42760,42761],{"class":1427},"  // Print to simulator console\n",[4937,42763],{},[863,42765,42767],{"id":42766},"common-pitfalls","Common Pitfalls",[871,42769,42770,42778],{},[874,42771,42772],{},[877,42773,42774,42776],{},[880,42775,14931],{},[880,42777,27663],{},[887,42779,42780,42788,42800,42808,42816],{},[877,42781,42782,42785],{},[892,42783,42784],{},"App crashes with \"Out of Memory\"",[892,42786,42787],{},"Reduce bitmap sizes, avoid string concatenation",[877,42789,42790,42793],{},[892,42791,42792],{},"Font looks wrong on some devices",[892,42794,42795,42796,42799],{},"Use ",[895,42797,42798],{},"Graphics.FONT_*"," constants, not custom fonts",[877,42801,42802,42805],{},[892,42803,42804],{},"Heart rate shows \"--\"",[892,42806,42807],{},"Normal when sensor not active - handle gracefully",[877,42809,42810,42813],{},[892,42811,42812],{},"Build fails with \"device not found\"",[892,42814,42815],{},"Check device ID matches SDK and manifest",[877,42817,42818,42821],{},[892,42819,42820],{},"Colors look different on watch",[892,42822,42823],{},"AMOLED screens show colors differently than LCD",[4937,42825],{},[863,42827,18681],{"id":18680},[842,42829,42830],{},"Building a Garmin watch face turned out to be a straightforward project that delivered real marketing value. The Connect IQ platform is well-documented, and the Monkey C language is approachable for anyone with JavaScript or Java experience. We went from zero Garmin development experience to a published watch face in just a few days.",[842,42832,42833,42834,42837],{},"For ",[846,42835,40880],{"href":40878,"rel":42836},[850],", this watch face serves a simple but important purpose: brand visibility. Every time a user checks the time, they see the BeatBuddy logo. It's a subtle touchpoint that reinforces brand awareness without requiring any complex functionality.",[1074,42839,42841],{"id":42840},"the-broader-opportunity","The Broader Opportunity",[842,42843,42844],{},"Beyond watch faces, Garmin's Connect IQ platform opens doors to more sophisticated integrations:",[958,42846,42847,42853,42859,42865],{},[961,42848,42849,42852],{},[996,42850,42851],{},"Data Fields"," - Custom metrics displayed during activities (imagine showing BeatBuddy cadence data)",[961,42854,42855,42858],{},[996,42856,42857],{},"Widgets"," - Quick-glance information cards",[961,42860,42861,42864],{},[996,42862,42863],{},"Full Apps"," - Complete applications with user interaction",[961,42866,42867,42870],{},[996,42868,42869],{},"Device Apps"," - Background services that can communicate with external devices",[842,42872,42873],{},"For sports tech companies, this represents a direct channel to your target audience - athletes who are already engaged with their training data.",[1074,42875,42877],{"id":42876},"what-we-covered","What We Covered",[958,42879,42880,42883,42886,42889,42892],{},[961,42881,42882],{},"Setting up the Connect IQ SDK and development environment",[961,42884,42885],{},"Understanding Monkey C language basics",[961,42887,42888],{},"Building a watch face with time, date, battery, and branding",[961,42890,42891],{},"Accessing fitness data from Garmin sensors",[961,42893,42894],{},"Deploying via USB and the Connect IQ Store",[4937,42896],{},[863,42898,18693],{"id":18692},[958,42900,42901,42908,42915,42921,42928],{},[961,42902,42903],{},[846,42904,42907],{"href":42905,"rel":42906},"https://developer.garmin.com/connect-iq/api-docs/",[850],"Connect IQ SDK Documentation",[961,42909,42910],{},[846,42911,42914],{"href":42912,"rel":42913},"https://developer.garmin.com/connect-iq/monkey-c/",[850],"Monkey C Language Reference",[961,42916,42917],{},[846,42918,42920],{"href":42095,"rel":42919},[850],"Compatible Devices List",[961,42922,42923],{},[846,42924,42927],{"href":42925,"rel":42926},"https://forums.garmin.com/developer/connect-iq/",[850],"Connect IQ Developer Forum",[961,42929,42930,42933],{},[846,42931,40880],{"href":40878,"rel":42932},[850]," - The programmable tempo trainer for swimmers",[1680,42935,42936],{},"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 .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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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}",{"title":728,"searchDepth":729,"depth":729,"links":42938},[42939,42940,42941,42942,42943,42948,42949,42953,42954,42955,42956,42960,42963,42967,42970,42976,42977,42981],{"id":27939,"depth":729,"text":27940},{"id":40937,"depth":729,"text":40938},{"id":40970,"depth":729,"text":40971},{"id":15347,"depth":729,"text":15348},{"id":41066,"depth":729,"text":41067,"children":42944},[42945,42946,42947],{"id":41070,"depth":1112,"text":41071},{"id":41103,"depth":1112,"text":41104},{"id":41164,"depth":1112,"text":41165},{"id":41256,"depth":729,"text":41257},{"id":41271,"depth":729,"text":41272,"children":42950},[42951,42952],{"id":41282,"depth":1112,"text":41283},{"id":41473,"depth":1112,"text":41474},{"id":41527,"depth":729,"text":41528},{"id":41625,"depth":729,"text":41626},{"id":41909,"depth":729,"text":41910},{"id":42102,"depth":729,"text":42103,"children":42957},[42958,42959],{"id":42106,"depth":1112,"text":42107},{"id":42125,"depth":1112,"text":42126},{"id":42197,"depth":729,"text":42198,"children":42961},[42962],{"id":42244,"depth":1112,"text":42245},{"id":42276,"depth":729,"text":42277,"children":42964},[42965,42966],{"id":42280,"depth":1112,"text":42281},{"id":42337,"depth":1112,"text":42338},{"id":42427,"depth":729,"text":42428,"children":42968},[42969],{"id":42559,"depth":1112,"text":42560},{"id":42645,"depth":729,"text":42646,"children":42971},[42972,42973,42974,42975],{"id":42649,"depth":1112,"text":42650},{"id":42682,"depth":1112,"text":41008},{"id":42702,"depth":1112,"text":42703},{"id":42726,"depth":1112,"text":42727},{"id":42766,"depth":729,"text":42767},{"id":18680,"depth":729,"text":18681,"children":42978},[42979,42980],{"id":42840,"depth":1112,"text":42841},{"id":42876,"depth":1112,"text":42877},{"id":18692,"depth":729,"text":18693},"sportstech","2026-01-22T00:00:00.000Z","Learn how to build a custom Garmin watch face using the Connect IQ SDK and Monkey C. A step-by-step tutorial based on our BeatBuddy watch face experiment.",{"src":42986},"/images/blog/musictechlab_blog_garmin-watchface-development.webp",{"enabled":738,"items":42988},[42989,42991,42994,42997],{"text":42990,"icon":5365},"One Monkey C codebase compiles to 67+ Garmin watch models automatically.",{"text":42992,"icon":42993},"A branded watch face went from zero experience to published in just a few days.","i-lucide-watch",{"text":42995,"icon":42996},"Garmin fitness APIs expose heart rate, steps, distance, and calories for watch faces.","i-lucide-heart-pulse",{"text":42998,"icon":3271},"Watch faces update once per minute by default to preserve battery life.",{},{"title":711,"description":42984},[42982,18784],"Zem2Aqf-MzspsssY3rKss9dqd13vbqAZEAxVBKJlKAU",{"id":43004,"title":289,"authors":43005,"badge":43008,"body":43010,"category":5678,"client":723,"date":43240,"description":5679,"extension":734,"faq":723,"featured":738,"featuredOrder":3520,"hidden":69,"image":43241,"keyTakeaways":723,"meta":43243,"navigation":738,"path":290,"seo":43244,"status":723,"stem":291,"tags":43245,"teaser":723,"__hash__":43246},"posts/blog/newsletter/musictech-insights-7-curated-by-mariusz-smenzyk.md",[43006],{"name":834,"to":720,"avatar":43007},{"src":722},{"label":43009,"color":5535},"#7",{"type":725,"value":43011,"toc":43233},[43012,43016,43023,43025,43028,43033,43038,43043,43085,43090,43092,43096,43105,43108,43114,43120,43124,43142,43145,43151,43157,43172,43175,43182,43185,43188,43196,43199,43205,43209,43218,43221,43224,43227],[5539,43013,43015],{"id":43014},"musictech-insights-7-curated-by-mariusz-smenżyk","MusicTech Insights #7 | Curated by Mariusz Smenżyk",[842,43017,43018,43019,43022],{},"This issue is curated by ",[846,43020,834],{"href":720,"rel":43021},[850],", co-founder and Technical Partner at MusicTech Lab. Expect slightly more technical than usual. If you like your MusicTech with a bit of code and a dash of reality check, this one’s for you.",[4937,43024],{},[863,43026,289],{"id":43027},"low-code-magic-wont-solve-musictech-reality",[842,43029,43030],{},[964,43031,43032],{},"Hi, I’m Mariusz. I work directly with teams building technology for the music industry, from early prototypes to production systems that must scale, stay reliable, and survive real-world complexity.",[842,43034,43035],{},[964,43036,43037],{},"Building in MusicTech has never been faster or more experimental. AI agents help write code, prototypes ship before specs are finished, and “vibe coding” often replaces long architectural debates. Momentum matters. Speed matters. Shipping matters. But speed without structure is dangerous. Rights, royalties, metadata, and trust don’t pause for experimentation. Faster builds can easily become fragile systems.",[842,43039,43040],{},[964,43041,43042],{},"Looking toward 2026, I see clear signals that separate teams who will ship reliable products from those chasing hype.",[991,43044,43045,43053,43061,43069,43077],{},[961,43046,43047],{},[964,43048,43049,43052],{},[996,43050,43051],{},"Agentic infrastructure becomes standard",": AI agents will act more like team members, with roles, limits, budgets, and rules. Managing them safely is essential.",[961,43054,43055],{},[964,43056,43057,43060],{},[996,43058,43059],{},"MCP management becomes a discipline",": More tools expose APIs. The challenge is no longer just connecting systems, but reliably orchestrating and controlling them.",[961,43062,43063],{},[964,43064,43065,43068],{},[996,43066,43067],{},"Focus on near-deterministic AI systems",": When money, rights, or compliance are involved, reproducible, predictable results matter more than creative variation.",[961,43070,43071],{},[964,43072,43073,43076],{},[996,43074,43075],{},"Platform engineering adapts to AI workflows",": Internal platforms will treat agents as core components, with clear ownership and lifecycle management.",[961,43078,43079],{},[964,43080,43081,43084],{},[996,43082,43083],{},"Shift from prototype-first to production-ready tooling",": Quick demos are useful, but real value comes from hardening, testing, and running AI in production.",[842,43086,43087],{},[964,43088,43089],{},"Below is my handpicked selection of insights worth paying attention to. They may be slightly more technical than usual, but they reflect what actually matters if you’re building, scaling, or investing in MusicTech products this year.",[4937,43091],{},[863,43093,43095],{"id":43094},"spotify-data-dump-big-drama-small-impact","Spotify Data Dump: Big Drama, Small Impact",[842,43097,43098,43099,43104],{},"A pirate group ",[846,43100,43103],{"href":43101,"rel":43102},"https://www.theguardian.com/technology/2025/dec/22/activist-group-says-it-has-scraped-86m-music-files-from-spotify",[850],"scraped Spotify"," and released massive metadata dumps. Spotify confirmed the unauthorised scraping and quickly pulled the plug, saving the world from a very expensive “side project.”",[842,43106,43107],{},"Even from a research or defensive perspective, the scale is overwhelming. You are immediately in full data-lake territory with massive storage, chunked ingestion, deduplication, and metadata indexing. The real challenge is processing the files: extracting audio features, cleaning messy metadata, and running quality checks. Anything beyond a tiny sample needs serious distributed systems.",[842,43109,43110,43111,43113],{},"Analysing 300+ terabytes of data is not a side project. A full pass of feature extraction across 86 million tracks could cost hundreds of thousands of dollars in cloud infrastructure before accounting for engineering, QA, or reruns. At that point, you are basically running a full-scale music analytics platform without any rights to do so.",[42386,43112],{},"\nThe bottom line is simple. This leak may sound impressive, but it is mostly noise. For the industry, it changes almost nothing.",[842,43115,43116],{},[846,43117,5605],{"href":43118,"rel":43119},"https://annas-archive.org/blog/backing-up-spotify.html",[850],[863,43121,43123],{"id":43122},"the-backstage-playbook-what-musictech-founders-can-actually-steal-from-spotify","The Backstage Playbook: What MusicTech Founders Can Actually Steal from Spotify",[842,43125,43126,43127,43132,43133,43138,43139,43141],{},"Spotify ",[846,43128,43131],{"href":43129,"rel":43130},"https://docs.google.com/document/d/1VykN1rmK-tbt6rKBqoGwltV9vqPCO1hdhpebmWF9q6Y/edit#",[850],"Backstage"," is now ",[846,43134,43137],{"href":43135,"rel":43136},"https://engineering.atspotify.com/2025/04/celebrating-five-years-of-backstage/",[850],"five years old",", and it’s easy to underestimate its value. What started as an internal developer portal became an open-source framework: a single place for services, APIs, documentation, and operational tools.",[42386,43140],{},"\nFor founders, the lesson is simple: complexity kills speed. Backstage shows how Spotify’s teams turned messy systems, tribal knowledge, and scattered tools into something scalable and understandable. It’s not a silver bullet, but it proves that investing in internal clarity pays off.",[842,43143,43144],{},"If your MusicTech company manages catalogs, AI pipelines, or complex integrations, ignoring Backstage-style organization is like ignoring accounting until your books explode. Less hidden knowledge, clear ownership between platform and product teams, and a single source of truth make running complex systems less painful and faster to scale.",[842,43146,43147],{},[846,43148,5605],{"href":43149,"rel":43150},"https://backstage.io/docs/overview/what-is-backstage",[850],[863,43152,43154],{"id":43153},"mcp-agents-in-musictech-epidemic-sound-example",[996,43155,43156],{},"MCP agents in MusicTech (Epidemic Sound example)",[842,43158,43159,43160,43165,43166,43171],{},"More tools are exposing their APIs and endpoints. ",[846,43161,43164],{"href":43162,"rel":43163},"https://modelcontextprotocol.io/docs/getting-started/intro",[850],"Model Context Protocol (MCP)"," acts like a standard port that lets AI apps securely call real tools and data sources instead of guessing. ",[846,43167,43170],{"href":43168,"rel":43169},"https://www.epidemicsound.com/blog/mcp-server/",[850],"Epidemic Sound’s new MCP server"," is a good example. It focuses on music discovery within creative workflows.",[842,43173,43174],{},"![Model Context Protocol (MCP) Structure](/images/blog/musictechlab_blog_model_context protocol_structure.webp)",[842,43176,43177,43178,43181],{},"For creative teams, this removes friction. Instead of switching between search tools, playlists, and messages, they can simply ask: “",[964,43179,43180],{},"Find tracks like X, but a bit less intense and slightly faster.","” This becomes a repeatable system action rather than a one-off search. Agents can prepare shortlists, explain why tracks fit, and pass the final choice to a human. Taste stays human, but the process is faster and simpler. MCP turns AI from a chat tool into a working tool. Decisions are quicker, workflows are repeatable, and results are easier to trust.",[842,43183,43184],{},"I asked myself how easy it would be to build an MCP server like Epidemic Sound.",[842,43186,43187],{},"LTDR: A prototype is easy, production is harder.",[842,43189,43190,43191,43195],{},"A prototype can take a few hours to a few days. If you already have an API for search, metadata, and preview URLs, you can wrap it as MCP tools quickly using something like ",[846,43192,1340],{"href":43193,"rel":43194},"https://gofastmcp.com/getting-started/welcome",[850],". The MCP layer is mostly about defining tools and handling requests and responses.",[842,43197,43198],{},"A production version can take weeks. Epidemic’s server is a safe and well-controlled bridge to their catalog. This means handling the “boring but important” parts properly: authentication, limits, caching, monitoring, and usage rules. Most of the work and time go there.",[842,43200,43201],{},[846,43202,5605],{"href":43203,"rel":43204},"https://www.musictechlab.io/blog/music-data/how-to-use-epidemic-sound-mcp-with-claude",[850],[863,43206,43208],{"id":43207},"no-code-vs-reality-why-musictech-still-ends-up-custom","No-Code vs Reality: Why MusicTech Still Ends Up Custom",[842,43210,43211,43212,43217],{},"AI app ",[846,43213,43216],{"href":43214,"rel":43215},"https://trickle.so/blog/lovable-ai-review",[850],"builders like Lovable are getting better",". You can go from idea to something that looks production-ready in days, not weeks. That speed is real and useful. The problem is that good-looking demos create false confidence about how close you are to a real product.",[842,43219,43220],{},"As soon as real users show up, the illusion breaks. Authentication, roles, multi-tenant data separation, audit logs, and background jobs are not advanced features. They are table stakes in MusicTech, especially in rights, licensing, analytics, and B2B tools. These issues rarely appear on screen, but they decide whether a system survives contact with reality.",[842,43222,43223],{},"This is why the music industry keeps rebuilding custom software. Not out of habit, but out of necessity. MusicTech is not one business model. It is a collection of overlapping businesses with incompatible rules, contracts, and exceptions. Generic tools fail exactly where the real value lives: in workflows, edge cases, and controlled data flow.",[842,43225,43226],{},"AI tools are great for learning fast and testing ideas. But in 2026, if your product touches money, rights, or trust, prompts are not a strategy. Ownership, architecture, and deliberate design still win.",[842,43228,43229],{},[846,43230,5605],{"href":43231,"rel":43232},"https://medium.com/@jacoballen_/trouble-with-lovable-3f10e72073c4",[850],{"title":728,"searchDepth":729,"depth":729,"links":43234},[43235,43236,43237,43238,43239],{"id":43027,"depth":729,"text":289},{"id":43094,"depth":729,"text":43095},{"id":43122,"depth":729,"text":43123},{"id":43153,"depth":729,"text":43156},{"id":43207,"depth":729,"text":43208},"2026-01-20T00:00:00.000Z",{"src":43242},"/images/blog/musictechlab_blog_musictech-insights_7_mariusz_smenzyk.webp",{},{"title":289,"description":5679},[5678,5523],"fZjZwWt4N4EjYwfNH8RIaVcnqTAvxlsBqcqEjLJ9qwk",{"id":43248,"title":707,"authors":43249,"badge":723,"body":43252,"category":42982,"client":723,"date":44481,"description":44482,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":44483,"keyTakeaways":44485,"meta":44497,"navigation":738,"path":708,"seo":44498,"status":723,"stem":709,"tags":44499,"teaser":723,"__hash__":44500},"posts/blog/sportstech/beatbuddy-replay-video-analysis-app-for-swimmers-flutter.md",[43250],{"name":834,"to":720,"avatar":43251},{"src":722},{"type":725,"value":43253,"toc":44456},[43254,43256,43266,43269,43271,43275,43279,43293,43297,43320,43324,43332,43336,43347,43349,43353,43356,43362,43368,43374,43380,43383,43385,43389,43392,43396,43399,43402,43408,43412,43415,43425,43465,43468,43473,43477,43480,43487,43691,43694,43699,43703,43706,43976,43979,43984,43988,43991,44072,44075,44080,44084,44087,44090,44095,44099,44102,44167,44170,44175,44179,44182,44185,44190,44194,44197,44263,44266,44271,44275,44278,44313,44316,44321,44323,44325,44328,44381,44383,44385,44388,44408,44410,44414,44420,44426,44432,44438,44441,44443,44453],[863,43255,27940],{"id":27939},[842,43257,43258,43261,43262,43265],{},[996,43259,43260],{},"BeatBuddy Replay"," is a video analysis application designed for athletes, with a special focus on swimmers training with the Total Immersion method. It was originally named ",[996,43263,43264],{},"SwimLab Coach's Eye",". We are currently integrating it into the BeatBuddy ecosystem - family of products for athletes - which is why we decided to rename it.",[842,43267,43268],{},"The app was built with Flutter. It is currently available on iOS (iPhone and iPad), with an Android version in development.",[4937,43270],{},[863,43272,43274],{"id":43273},"what-can-beatbuddy-replay-do","What Can BeatBuddy Replay Do?",[1074,43276,43278],{"id":43277},"video-playback","Video Playback",[958,43280,43281,43284,43287,43290],{},[961,43282,43283],{},"Frame-by-frame playback",[961,43285,43286],{},"Speed adjustment from 0.25x to 2x (slow motion)",[961,43288,43289],{},"Forward and backward scrubbing",[961,43291,43292],{},"Audio mute",[1074,43294,43296],{"id":43295},"drawing-tools","Drawing Tools",[958,43298,43299,43302,43305,43308,43311,43314,43317],{},[961,43300,43301],{},"Straight lines and arrows",[961,43303,43304],{},"Rectangles and circles",[961,43306,43307],{},"Triangles with automatic angle measurement",[961,43309,43310],{},"Curves (freehand drawing)",[961,43312,43313],{},"Protractor",[961,43315,43316],{},"Color and line thickness selection",[961,43318,43319],{},"Undo/Redo",[1074,43321,43323],{"id":43322},"analysis-and-focus","Analysis and Focus",[958,43325,43326,43329],{},[961,43327,43328],{},"Mask mode (spotlight) - dims the background, allowing focus on a selected area",[961,43330,43331],{},"Swimming technique angle measurement",[1074,43333,43335],{"id":43334},"sharing","Sharing",[958,43337,43338,43341,43344],{},[961,43339,43340],{},"Screenshots with annotations",[961,43342,43343],{},"Analytical session recording",[961,43345,43346],{},"Sharing via system Share menu",[4937,43348],{},[863,43350,43352],{"id":43351},"who-is-this-app-for","Who Is This App For?",[842,43354,43355],{},"BeatBuddy Replay was created for:",[842,43357,43358,43361],{},[996,43359,43360],{},"Total Immersion Coaches"," - who need precise tools for technique analysis. The TI method is based on conscious work on every movement element, and the app allows coaches to show athletes exactly what and how to improve.",[842,43363,43364,43367],{},[996,43365,43366],{},"Swimming Coaches"," - who want to show athletes their technical errors directly on the recording.",[842,43369,43370,43373],{},[996,43371,43372],{},"Athletes"," - who independently analyze their training sessions and competitions.",[842,43375,43376,43379],{},[996,43377,43378],{},"Swimming Clubs"," - looking for a simple video analysis tool without complicated software.",[842,43381,43382],{},"The app fills the gap between a simple video player and professional (and expensive) sports analysis software. It's intuitive, works offline, and requires no subscription.",[4937,43384],{},[863,43386,43388],{"id":43387},"_10-technical-challenges","10 Technical Challenges",[842,43390,43391],{},"During two weeks of test development, we encountered many problems. Here are the 10 most important challenges and the lessons we learned from them.",[1074,43393,43395],{"id":43394},"_1-speed-vs-architecture-the-mvp-dilemma","1. Speed vs Architecture - The MVP Dilemma",[842,43397,43398],{},"All the application code is in a single file with over 3,300 lines. Without an advanced state management framework, everything relies on Flutter's basic mechanisms.",[842,43400,43401],{},"We wanted to deliver a working product to coaches who were waiting for this tool as quickly as possible. Perfect architecture can wait - user feedback cannot.",[842,43403,43404,43407],{},[996,43405,43406],{},"Takeaway:"," For MVP projects, speed is more important than architecture, but refactoring will be essential for further development.",[1074,43409,43411],{"id":43410},"_2-ios-crashes-hidden-platform-requirements","2. iOS Crashes - Hidden Platform Requirements",[842,43413,43414],{},"One of the first serious problems was app crashes on iOS. The debugging process was tedious and required many attempts before we could locate the issue.",[842,43416,43417,43418,28366,43421,43424],{},"It turned out that plugins like ",[895,43419,43420],{},"video_player",[895,43422,43423],{},"file_picker"," require appropriate entries in the iOS configuration:",[1013,43426,43428],{"className":11154,"code":43427,"language":11157,"meta":728,"style":728},"\u003Ckey>NSPhotoLibraryUsageDescription\u003C/key>\n\u003Cstring>Needed to access videos for analysis\u003C/string>\n",[895,43429,43430,43448],{"__ignoreMap":728},[1086,43431,43432,43434,43437,43439,43442,43444,43446],{"class":1088,"line":1089},[1086,43433,11164],{"class":1146},[1086,43435,43436],{"class":4109},"key",[1086,43438,2694],{"class":1146},[1086,43440,43441],{"class":1436},"NSPhotoLibraryUsageDescription",[1086,43443,11201],{"class":1146},[1086,43445,43436],{"class":4109},[1086,43447,11170],{"class":1146},[1086,43449,43450,43452,43454,43456,43459,43461,43463],{"class":1088,"line":729},[1086,43451,11164],{"class":1146},[1086,43453,18489],{"class":4109},[1086,43455,2694],{"class":1146},[1086,43457,43458],{"class":1436},"Needed to access videos for analysis",[1086,43460,11201],{"class":1146},[1086,43462,18489],{"class":4109},[1086,43464,11170],{"class":1146},[842,43466,43467],{},"Every crash is a potentially lost user. A coach whose app crashes during a demonstration to an athlete won't give it a second chance.",[842,43469,43470,43472],{},[996,43471,43406],{}," Cross-platform plugins often require platform-specific configuration that isn't obvious from the documentation.",[1074,43474,43476],{"id":43475},"_3-shape-drawing-building-a-custom-graphics-engine","3. Shape Drawing - Building a Custom Graphics Engine",[842,43478,43479],{},"From the first version of the app, we had to implement a complex drawing system supporting 6 shape types: lines, arrows, rectangles, circles, triangles, and freehand curves.",[842,43481,43482,43483,43486],{},"Flutter offers the ",[895,43484,43485],{},"CustomPainter"," class, which provides full control over drawing:",[1013,43488,43492],{"className":43489,"code":43490,"language":43491,"meta":728,"style":728},"language-dart shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","class DrawingPainter extends CustomPainter {\n  final List\u003CShape> shapes;\n\n  @override\n  void paint(Canvas canvas, Size size) {\n    for (var shape in shapes) {\n      final paint = Paint()\n        ..color = shape.color\n        ..strokeWidth = shape.strokeWidth\n        ..style = PaintingStyle.stroke;\n\n      canvas.drawLine(shape.start, shape.end, paint);\n    }\n  }\n}\n","dart",[895,43493,43494,43508,43526,43530,43535,43559,43575,43590,43608,43624,43643,43647,43679,43683,43687],{"__ignoreMap":728},[1086,43495,43496,43498,43501,43503,43506],{"class":1088,"line":1089},[1086,43497,4036],{"class":1146},[1086,43499,43500],{"class":1092}," DrawingPainter",[1086,43502,41548],{"class":1146},[1086,43504,43505],{"class":1092}," CustomPainter",[1086,43507,1164],{"class":1436},[1086,43509,43510,43513,43516,43518,43521,43524],{"class":1088,"line":729},[1086,43511,43512],{"class":1155},"  final",[1086,43514,43515],{"class":1092}," List",[1086,43517,11164],{"class":1436},[1086,43519,43520],{"class":1092},"Shape",[1086,43522,43523],{"class":1436},"> shapes",[1086,43525,33466],{"class":1146},[1086,43527,43528],{"class":1088,"line":1112},[1086,43529,3390],{"emptyLinePlaceholder":738},[1086,43531,43532],{"class":1088,"line":1181},[1086,43533,43534],{"class":1155},"  @override\n",[1086,43536,43537,43540,43543,43545,43548,43551,43553,43556],{"class":1088,"line":1205},[1086,43538,43539],{"class":1155},"  void",[1086,43541,43542],{"class":1105}," paint",[1086,43544,1398],{"class":1436},[1086,43546,43547],{"class":1092},"Canvas",[1086,43549,43550],{"class":1436}," canvas",[1086,43552,1227],{"class":1146},[1086,43554,43555],{"class":1092}," Size",[1086,43557,43558],{"class":1436}," size) {\n",[1086,43560,43561,43563,43565,43567,43570,43572],{"class":1088,"line":1276},[1086,43562,5925],{"class":1423},[1086,43564,5979],{"class":1436},[1086,43566,38651],{"class":1155},[1086,43568,43569],{"class":1436}," shape ",[1086,43571,5931],{"class":1423},[1086,43573,43574],{"class":1436}," shapes) {\n",[1086,43576,43577,43580,43583,43585,43588],{"class":1088,"line":1282},[1086,43578,43579],{"class":1155},"      final",[1086,43581,43582],{"class":1436}," paint ",[1086,43584,1440],{"class":1146},[1086,43586,43587],{"class":1092}," Paint",[1086,43589,1387],{"class":1436},[1086,43591,43592,43595,43598,43600,43603,43605],{"class":1088,"line":1288},[1086,43593,43594],{"class":1146},"        ..",[1086,43596,43597],{"class":1436},"color ",[1086,43599,1440],{"class":1146},[1086,43601,43602],{"class":1436}," shape",[1086,43604,861],{"class":1146},[1086,43606,43607],{"class":1436},"color\n",[1086,43609,43610,43612,43615,43617,43619,43621],{"class":1088,"line":2685},[1086,43611,43594],{"class":1146},[1086,43613,43614],{"class":1436},"strokeWidth ",[1086,43616,1440],{"class":1146},[1086,43618,43602],{"class":1436},[1086,43620,861],{"class":1146},[1086,43622,43623],{"class":1436},"strokeWidth\n",[1086,43625,43626,43628,43631,43633,43636,43638,43641],{"class":1088,"line":2700},[1086,43627,43594],{"class":1146},[1086,43629,43630],{"class":1436},"style ",[1086,43632,1440],{"class":1146},[1086,43634,43635],{"class":1092}," PaintingStyle",[1086,43637,861],{"class":1146},[1086,43639,43640],{"class":1436},"stroke",[1086,43642,33466],{"class":1146},[1086,43644,43645],{"class":1088,"line":3398},[1086,43646,3390],{"emptyLinePlaceholder":738},[1086,43648,43649,43652,43654,43657,43660,43662,43664,43666,43668,43670,43672,43674,43677],{"class":1088,"line":1715},[1086,43650,43651],{"class":1436},"      canvas",[1086,43653,861],{"class":1146},[1086,43655,43656],{"class":1105},"drawLine",[1086,43658,43659],{"class":1436},"(shape",[1086,43661,861],{"class":1146},[1086,43663,29437],{"class":1436},[1086,43665,1227],{"class":1146},[1086,43667,43602],{"class":1436},[1086,43669,861],{"class":1146},[1086,43671,29457],{"class":1436},[1086,43673,1227],{"class":1146},[1086,43675,43676],{"class":1436}," paint)",[1086,43678,33466],{"class":1146},[1086,43680,43681],{"class":1088,"line":3409},[1086,43682,1279],{"class":1436},[1086,43684,43685],{"class":1088,"line":3415},[1086,43686,1285],{"class":1436},[1086,43688,43689],{"class":1088,"line":3421},[1086,43690,1291],{"class":1436},[842,43692,43693],{},"Coaches need different tools for different situations. An arrow shows the direction of movement, a circle highlights an error, a line compares body positions.",[842,43695,43696,43698],{},[996,43697,43406],{}," Flutter offers a powerful drawing API, but it requires knowledge of geometry. It's an investment that pays off - a custom engine provides full control over functionality.",[1074,43700,43702],{"id":43701},"_4-angle-measurement-mathematics-in-service-of-sport","4. Angle Measurement - Mathematics in Service of Sport",[842,43704,43705],{},"A key feature for swimming coaches - technique angle measurement - required implementing vector mathematics. We use the dot product of vectors and the law of cosines:",[1013,43707,43709],{"className":43489,"code":43708,"language":43491,"meta":728,"style":728},"double calculateAngle(Offset p1, Offset vertex, Offset p2) {\n  final v1 = p1 - vertex;\n  final v2 = p2 - vertex;\n\n  final dotProduct = v1.dx * v2.dx + v1.dy * v2.dy;\n  final magnitude1 = sqrt(v1.dx * v1.dx + v1.dy * v1.dy);\n  final magnitude2 = sqrt(v2.dx * v2.dx + v2.dy * v2.dy);\n\n  final cosine = dotProduct / (magnitude1 * magnitude2);\n  return acos(cosine) * (180 / pi); // result in degrees\n}\n",[895,43710,43711,43742,43760,43778,43782,43828,43874,43918,43922,43945,43972],{"__ignoreMap":728},[1086,43712,43713,43716,43719,43721,43724,43727,43729,43732,43735,43737,43739],{"class":1088,"line":1089},[1086,43714,43715],{"class":1092},"double",[1086,43717,43718],{"class":1105}," calculateAngle",[1086,43720,1398],{"class":1436},[1086,43722,43723],{"class":1092},"Offset",[1086,43725,43726],{"class":1436}," p1",[1086,43728,1227],{"class":1146},[1086,43730,43731],{"class":1092}," Offset",[1086,43733,43734],{"class":1436}," vertex",[1086,43736,1227],{"class":1146},[1086,43738,43731],{"class":1092},[1086,43740,43741],{"class":1436}," p2) {\n",[1086,43743,43744,43746,43749,43751,43754,43756,43758],{"class":1088,"line":729},[1086,43745,43512],{"class":1155},[1086,43747,43748],{"class":1436}," v1 ",[1086,43750,1440],{"class":1146},[1086,43752,43753],{"class":1436}," p1 ",[1086,43755,2635],{"class":1146},[1086,43757,43734],{"class":1436},[1086,43759,33466],{"class":1146},[1086,43761,43762,43764,43767,43769,43772,43774,43776],{"class":1088,"line":1112},[1086,43763,43512],{"class":1155},[1086,43765,43766],{"class":1436}," v2 ",[1086,43768,1440],{"class":1146},[1086,43770,43771],{"class":1436}," p2 ",[1086,43773,2635],{"class":1146},[1086,43775,43734],{"class":1436},[1086,43777,33466],{"class":1146},[1086,43779,43780],{"class":1088,"line":1181},[1086,43781,3390],{"emptyLinePlaceholder":738},[1086,43783,43784,43786,43789,43791,43794,43796,43799,43801,43804,43806,43808,43810,43812,43814,43817,43819,43821,43823,43826],{"class":1088,"line":1205},[1086,43785,43512],{"class":1155},[1086,43787,43788],{"class":1436}," dotProduct ",[1086,43790,1440],{"class":1146},[1086,43792,43793],{"class":1436}," v1",[1086,43795,861],{"class":1146},[1086,43797,43798],{"class":1436},"dx ",[1086,43800,2775],{"class":1146},[1086,43802,43803],{"class":1436}," v2",[1086,43805,861],{"class":1146},[1086,43807,43798],{"class":1436},[1086,43809,30708],{"class":1146},[1086,43811,43793],{"class":1436},[1086,43813,861],{"class":1146},[1086,43815,43816],{"class":1436},"dy ",[1086,43818,2775],{"class":1146},[1086,43820,43803],{"class":1436},[1086,43822,861],{"class":1146},[1086,43824,43825],{"class":1436},"dy",[1086,43827,33466],{"class":1146},[1086,43829,43830,43832,43835,43837,43840,43843,43845,43847,43849,43851,43853,43855,43857,43859,43861,43863,43865,43867,43869,43872],{"class":1088,"line":1276},[1086,43831,43512],{"class":1155},[1086,43833,43834],{"class":1436}," magnitude1 ",[1086,43836,1440],{"class":1146},[1086,43838,43839],{"class":1105}," sqrt",[1086,43841,43842],{"class":1436},"(v1",[1086,43844,861],{"class":1146},[1086,43846,43798],{"class":1436},[1086,43848,2775],{"class":1146},[1086,43850,43793],{"class":1436},[1086,43852,861],{"class":1146},[1086,43854,43798],{"class":1436},[1086,43856,30708],{"class":1146},[1086,43858,43793],{"class":1436},[1086,43860,861],{"class":1146},[1086,43862,43816],{"class":1436},[1086,43864,2775],{"class":1146},[1086,43866,43793],{"class":1436},[1086,43868,861],{"class":1146},[1086,43870,43871],{"class":1436},"dy)",[1086,43873,33466],{"class":1146},[1086,43875,43876,43878,43881,43883,43885,43888,43890,43892,43894,43896,43898,43900,43902,43904,43906,43908,43910,43912,43914,43916],{"class":1088,"line":1282},[1086,43877,43512],{"class":1155},[1086,43879,43880],{"class":1436}," magnitude2 ",[1086,43882,1440],{"class":1146},[1086,43884,43839],{"class":1105},[1086,43886,43887],{"class":1436},"(v2",[1086,43889,861],{"class":1146},[1086,43891,43798],{"class":1436},[1086,43893,2775],{"class":1146},[1086,43895,43803],{"class":1436},[1086,43897,861],{"class":1146},[1086,43899,43798],{"class":1436},[1086,43901,30708],{"class":1146},[1086,43903,43803],{"class":1436},[1086,43905,861],{"class":1146},[1086,43907,43816],{"class":1436},[1086,43909,2775],{"class":1146},[1086,43911,43803],{"class":1436},[1086,43913,861],{"class":1146},[1086,43915,43871],{"class":1436},[1086,43917,33466],{"class":1146},[1086,43919,43920],{"class":1088,"line":1288},[1086,43921,3390],{"emptyLinePlaceholder":738},[1086,43923,43924,43926,43929,43931,43933,43935,43938,43940,43943],{"class":1088,"line":2685},[1086,43925,43512],{"class":1155},[1086,43927,43928],{"class":1436}," cosine ",[1086,43930,1440],{"class":1146},[1086,43932,43788],{"class":1436},[1086,43934,23036],{"class":1146},[1086,43936,43937],{"class":1436}," (magnitude1 ",[1086,43939,2775],{"class":1146},[1086,43941,43942],{"class":1436}," magnitude2)",[1086,43944,33466],{"class":1146},[1086,43946,43947,43949,43952,43955,43957,43959,43962,43964,43967,43969],{"class":1088,"line":2700},[1086,43948,4656],{"class":1423},[1086,43950,43951],{"class":1105}," acos",[1086,43953,43954],{"class":1436},"(cosine) ",[1086,43956,2775],{"class":1146},[1086,43958,5979],{"class":1436},[1086,43960,43961],{"class":1187},"180",[1086,43963,26689],{"class":1146},[1086,43965,43966],{"class":1436}," pi)",[1086,43968,4639],{"class":1146},[1086,43970,43971],{"class":1427}," // result in degrees\n",[1086,43973,43974],{"class":1088,"line":3398},[1086,43975,1291],{"class":1436},[842,43977,43978],{},"Elbow bend angle, hand entry angle into the water, head position relative to torso - these are concrete numbers that a coach can show to an athlete. \"Bend your elbow 15 degrees more\" is more effective than \"bend more.\"",[842,43980,43981,43983],{},[996,43982,43406],{}," Applications for a specific industry often require domain knowledge beyond typical programming.",[1074,43985,43987],{"id":43986},"_5-interactive-objects-dragging-and-resizing","5. Interactive Objects - Dragging and Resizing",[842,43989,43990],{},"Implementing dragging and resizing of drawn shapes proved to be one of the more difficult challenges. It requires detecting whether a finger touches a shape, tracking the \"selected\" element, and smoothly updating its position.",[1013,43992,43994],{"className":43489,"code":43993,"language":43491,"meta":728,"style":728},"bool isPointInsideShape(Shape shape, Offset point) {\n  final rect = Rect.fromPoints(shape.start, shape.end);\n  return rect.contains(point);\n}\n",[895,43995,43996,44017,44051,44068],{"__ignoreMap":728},[1086,43997,43998,44001,44004,44006,44008,44010,44012,44014],{"class":1088,"line":1089},[1086,43999,44000],{"class":1092},"bool",[1086,44002,44003],{"class":1105}," isPointInsideShape",[1086,44005,1398],{"class":1436},[1086,44007,43520],{"class":1092},[1086,44009,43602],{"class":1436},[1086,44011,1227],{"class":1146},[1086,44013,43731],{"class":1092},[1086,44015,44016],{"class":1436}," point) {\n",[1086,44018,44019,44021,44024,44026,44029,44031,44034,44036,44038,44040,44042,44044,44046,44049],{"class":1088,"line":729},[1086,44020,43512],{"class":1155},[1086,44022,44023],{"class":1436}," rect ",[1086,44025,1440],{"class":1146},[1086,44027,44028],{"class":1092}," Rect",[1086,44030,861],{"class":1146},[1086,44032,44033],{"class":1105},"fromPoints",[1086,44035,43659],{"class":1436},[1086,44037,861],{"class":1146},[1086,44039,29437],{"class":1436},[1086,44041,1227],{"class":1146},[1086,44043,43602],{"class":1436},[1086,44045,861],{"class":1146},[1086,44047,44048],{"class":1436},"end)",[1086,44050,33466],{"class":1146},[1086,44052,44053,44055,44058,44060,44063,44066],{"class":1088,"line":1112},[1086,44054,4656],{"class":1423},[1086,44056,44057],{"class":1436}," rect",[1086,44059,861],{"class":1146},[1086,44061,44062],{"class":1105},"contains",[1086,44064,44065],{"class":1436},"(point)",[1086,44067,33466],{"class":1146},[1086,44069,44070],{"class":1088,"line":1181},[1086,44071,1291],{"class":1436},[842,44073,44074],{},"A coach draws an arrow, but the athlete asks about a different moment - they need to quickly move the annotation without redrawing. Workflow fluidity is fundamental.",[842,44076,44077,44079],{},[996,44078,43406],{}," Interactive graphic elements are much harder than static drawing, but essential for good UX.",[1074,44081,44083],{"id":44082},"_6-spotlight-effect-focusing-viewer-attention","6. Spotlight Effect - Focusing Viewer Attention",[842,44085,44086],{},"Implementing the \"spotlight\" effect - dimming the background with a cut-out circle - required a multi-layered approach with two independent graphic components.",[842,44088,44089],{},"When a coach shows hand position, the rest of the frame is distracting. Spotlight allows focusing the athlete's attention exactly where needed. It's the difference between \"look here\" and actually looking.",[842,44091,44092,44094],{},[996,44093,43406],{}," Visual effects require thoughtful layer architecture, but dramatically improve application usability.",[1074,44096,44098],{"id":44097},"_7-screen-recording-platform-limitations","7. Screen Recording - Platform Limitations",[842,44100,44101],{},"Attempting to add screen recording revealed the painful truth about cross-platform development. The library used simply doesn't work the same on all systems.",[1013,44103,44105],{"className":43489,"code":44104,"language":43491,"meta":728,"style":728},"if (!Platform.isMacOS) {\n  await FlutterScreenRecording.startRecordScreen(fileName);\n} else {\n  showMessage(\"Screen recording is not available on this platform\");\n}\n",[895,44106,44107,44122,44140,44149,44163],{"__ignoreMap":728},[1086,44108,44109,44111,44113,44115,44117,44119],{"class":1088,"line":1089},[1086,44110,11056],{"class":1423},[1086,44112,5979],{"class":1436},[1086,44114,38428],{"class":1146},[1086,44116,5024],{"class":1092},[1086,44118,861],{"class":1146},[1086,44120,44121],{"class":1436},"isMacOS) {\n",[1086,44123,44124,44127,44130,44132,44135,44138],{"class":1088,"line":729},[1086,44125,44126],{"class":1423},"  await",[1086,44128,44129],{"class":1092}," FlutterScreenRecording",[1086,44131,861],{"class":1146},[1086,44133,44134],{"class":1105},"startRecordScreen",[1086,44136,44137],{"class":1436},"(fileName)",[1086,44139,33466],{"class":1146},[1086,44141,44142,44145,44147],{"class":1088,"line":1112},[1086,44143,44144],{"class":1436},"} ",[1086,44146,11084],{"class":1423},[1086,44148,1164],{"class":1436},[1086,44150,44151,44154,44156,44159,44161],{"class":1088,"line":1181},[1086,44152,44153],{"class":1105},"  showMessage",[1086,44155,1398],{"class":1436},[1086,44157,44158],{"class":1096},"\"Screen recording is not available on this platform\"",[1086,44160,1410],{"class":1436},[1086,44162,33466],{"class":1146},[1086,44164,44165],{"class":1088,"line":1205},[1086,44166,1291],{"class":1436},[842,44168,44169],{},"A coach wants to record their analysis with voice commentary and send it to an athlete. This is a key feature for remote work with athletes.",[842,44171,44172,44174],{},[996,44173,43406],{}," \"Cross-platform\" doesn't mean \"works identically everywhere.\" You need to test on all target devices and sometimes accept limitations.",[1074,44176,44178],{"id":44177},"_8-user-interface-dozens-of-iterations","8. User Interface - Dozens of Iterations",[842,44180,44181],{},"Finding the optimal control layout required many iterations. We started with controls in the right sidebar, then moved them to the bottom, added a play icon over the video, removed the sidebar, centered the toolbar...",[842,44183,44184],{},"A coach often holds the tablet with one hand while pointing something out to an athlete with the other. Controls must be accessible with a thumb, cannot obscure the video, and must be readable at the pool in full sunlight.",[842,44186,44187,44189],{},[996,44188,43406],{}," UX requires experimentation and feedback collection. You cannot design the perfect interface on the first try.",[1074,44191,44193],{"id":44192},"_9-responsiveness-iphone-vs-ipad","9. Responsiveness - iPhone vs iPad",[842,44195,44196],{},"The app must work equally well on iPhone (compact screen, on-the-go analysis) and iPad (larger screen, detailed work). Drawing controls require different proportions on each device.",[1013,44198,44200],{"className":43489,"code":44199,"language":43491,"meta":728,"style":728},"final isTablet = MediaQuery.of(context).size.shortestSide >= 600;\nfinal iconSize = isTablet ? 32.0 : 24.0;\n",[895,44201,44202,44240],{"__ignoreMap":728},[1086,44203,44204,44207,44210,44212,44215,44217,44220,44223,44225,44228,44230,44233,44235,44238],{"class":1088,"line":1089},[1086,44205,44206],{"class":1155},"final",[1086,44208,44209],{"class":1436}," isTablet ",[1086,44211,1440],{"class":1146},[1086,44213,44214],{"class":1092}," MediaQuery",[1086,44216,861],{"class":1146},[1086,44218,44219],{"class":1105},"of",[1086,44221,44222],{"class":1436},"(context)",[1086,44224,861],{"class":1146},[1086,44226,44227],{"class":1436},"size",[1086,44229,861],{"class":1146},[1086,44231,44232],{"class":1436},"shortestSide ",[1086,44234,2510],{"class":1146},[1086,44236,44237],{"class":1187}," 600",[1086,44239,33466],{"class":1146},[1086,44241,44242,44244,44247,44249,44251,44253,44256,44258,44261],{"class":1088,"line":729},[1086,44243,44206],{"class":1155},[1086,44245,44246],{"class":1436}," iconSize ",[1086,44248,1440],{"class":1146},[1086,44250,44209],{"class":1436},[1086,44252,38405],{"class":1146},[1086,44254,44255],{"class":1187}," 32.0",[1086,44257,33844],{"class":1146},[1086,44259,44260],{"class":1187}," 24.0",[1086,44262,33466],{"class":1146},[842,44264,44265],{},"A coach uses the iPad during training at the pool, but the athlete watches the analysis on their iPhone at home. The experience must be consistent.",[842,44267,44268,44270],{},[996,44269,43406],{}," Responsive design requires conscious design for each form factor, not just interface scaling.",[1074,44272,44274],{"id":44273},"_10-system-integration-sharing-and-export","10. System Integration - Sharing and Export",[842,44276,44277],{},"Every \"simple\" system integration - opening links, sharing, saving to gallery - turned out to require more work than we expected.",[1013,44279,44281],{"className":43489,"code":44280,"language":43491,"meta":728,"style":728},"await Share.shareFiles([screenshotPath], text: 'Technique analysis');\n",[895,44282,44283],{"__ignoreMap":728},[1086,44284,44285,44288,44291,44293,44296,44299,44301,44304,44306,44309,44311],{"class":1088,"line":1089},[1086,44286,44287],{"class":1423},"await",[1086,44289,44290],{"class":1092}," Share",[1086,44292,861],{"class":1146},[1086,44294,44295],{"class":1105},"shareFiles",[1086,44297,44298],{"class":1436},"([screenshotPath]",[1086,44300,1227],{"class":1146},[1086,44302,44303],{"class":1436}," text",[1086,44305,1133],{"class":1146},[1086,44307,44308],{"class":1096}," 'Technique analysis'",[1086,44310,1410],{"class":1436},[1086,44312,33466],{"class":1146},[842,44314,44315],{},"A coach takes a screenshot with annotations and wants to send it via WhatsApp. If that requires 5 steps instead of 2, they simply won't do it.",[842,44317,44318,44320],{},[996,44319,43406],{}," System integrations are the \"last mile\" - often underestimated, but crucial for real-world app usage.",[4937,44322],{},[863,44324,25699],{"id":8196},[842,44326,44327],{},"The development history of BeatBuddy Replay shows a typical product-building path:",[871,44329,44330,44339],{},[874,44331,44332],{},[877,44333,44334,44337],{},[880,44335,44336],{},"Phase",[880,44338,34627],{},[887,44340,44341,44349,44357,44365,44373],{},[877,44342,44343,44346],{},[892,44344,44345],{},"Quick start",[892,44347,44348],{},"Working app in one day",[877,44350,44351,44354],{},[892,44352,44353],{},"Platform issues",[892,44355,44356],{},"Crashes, debugging, iOS adaptation",[877,44358,44359,44362],{},[892,44360,44361],{},"Feature expansion",[892,44363,44364],{},"Spotlight, object movement, protractor",[877,44366,44367,44370],{},[892,44368,44369],{},"UX refinement",[892,44371,44372],{},"Dozens of interface iterations",[877,44374,44375,44378],{},[892,44376,44377],{},"Stabilization",[892,44379,44380],{},"Screen recording, sharing",[4937,44382],{},[863,44384,31693],{"id":16446},[842,44386,44387],{},"We are working on:",[958,44389,44390,44396,44402],{},[961,44391,44392,44395],{},[996,44393,44394],{},"Android version"," - will expand app availability",[961,44397,44398,44401],{},[996,44399,44400],{},"Two-video comparison"," - side-by-side analysis, crucial for the Total Immersion method",[961,44403,44404,44407],{},[996,44405,44406],{},"Video library"," - collection of links to the best exercises on YouTube and recordings of world-class swimmers' technique, which will serve as reference models for analysis",[4937,44409],{},[863,44411,44413],{"id":44412},"key-takeaways-for-other-developers","Key Takeaways for Other Developers",[842,44415,44416,44419],{},[996,44417,44418],{},"Speed over perfection"," - it's better to release a working product and iterate than to wait for the ideal.",[842,44421,44422,44425],{},[996,44423,44424],{},"Test on real devices"," - a simulator cannot replace an iPad in a coach's hands by the pool.",[842,44427,44428,44431],{},[996,44429,44430],{},"Listen to users"," - every UI iteration resulted from real feedback.",[842,44433,44434,44437],{},[996,44435,44436],{},"Cross-platform has its limits"," - accept limitations and communicate them clearly.",[842,44439,44440],{},"The app delivers a fully functional video analysis tool in just over 3,000 lines of code - proof that Flutter allows you to quickly build useful products for a specific niche.",[4937,44442],{},[842,44444,44445],{},[964,44446,44447,44448],{},"BeatBuddy Replay is part of the BeatBuddy Platform ecosystem, which also includes BeatBuddy Pro - a programmable metronome device for swimmers. More information at ",[846,44449,44452],{"href":44450,"rel":44451},"https://beatbuddypro.com",[850],"beatbuddypro.com",[1680,44454,44455],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":728,"searchDepth":729,"depth":729,"links":44457},[44458,44459,44465,44466,44478,44479,44480],{"id":27939,"depth":729,"text":27940},{"id":43273,"depth":729,"text":43274,"children":44460},[44461,44462,44463,44464],{"id":43277,"depth":1112,"text":43278},{"id":43295,"depth":1112,"text":43296},{"id":43322,"depth":1112,"text":43323},{"id":43334,"depth":1112,"text":43335},{"id":43351,"depth":729,"text":43352},{"id":43387,"depth":729,"text":43388,"children":44467},[44468,44469,44470,44471,44472,44473,44474,44475,44476,44477],{"id":43394,"depth":1112,"text":43395},{"id":43410,"depth":1112,"text":43411},{"id":43475,"depth":1112,"text":43476},{"id":43701,"depth":1112,"text":43702},{"id":43986,"depth":1112,"text":43987},{"id":44082,"depth":1112,"text":44083},{"id":44097,"depth":1112,"text":44098},{"id":44177,"depth":1112,"text":44178},{"id":44192,"depth":1112,"text":44193},{"id":44273,"depth":1112,"text":44274},{"id":8196,"depth":729,"text":25699},{"id":16446,"depth":729,"text":31693},{"id":44412,"depth":729,"text":44413},"2026-01-17T00:00:00.000Z","A deep-dive into building a Flutter video analysis app for swimmers. From MVP architecture to cross-platform challenges - lessons from two weeks of development.",{"src":44484},"/images/blog/musictechlab_blog_beatbuddy-replay-video-analysis-app-for-swimmers.webp",{"enabled":738,"items":44486},[44487,44489,44491,44494],{"text":44488,"icon":2939},"Flutter MVP was built in 2 weeks with 3,300 lines of code in a single file.",{"text":44490,"icon":4855},"Cross-platform plugins often require hidden platform-specific iOS/Android configuration.",{"text":44492,"icon":44493},"Vector math powers angle measurement, giving coaches precise technique feedback in degrees.","i-lucide-triangle",{"text":44495,"icon":44496},"Dozens of UI iterations were needed because coaches use tablets one-handed at the pool.","i-lucide-tablet",{},{"title":707,"description":44482},[42982,18784],"HN40SuTtPMzORZCJ7GzVwCBNWWL9c_yJcHijErrcNsU",{"id":44502,"title":586,"authors":44503,"badge":723,"body":44506,"category":756,"client":723,"date":45299,"description":45300,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":45301,"keyTakeaways":45303,"meta":45313,"navigation":738,"path":587,"seo":45314,"status":723,"stem":588,"tags":45315,"teaser":723,"__hash__":45316},"posts/blog/software-development/musicxml-standard-for-music-notation-and-education.md",[44504],{"name":834,"to":720,"avatar":44505},{"src":722},{"type":725,"value":44507,"toc":45273},[44508,44512,44515,44518,44522,44525,44580,44583,44689,44693,44697,44700,44704,44707,44711,44714,44718,44721,44725,44729,44732,44746,44750,44753,44757,44760,44764,44767,44781,44785,44789,44792,44964,44968,44971,45075,45079,45082,45086,45167,45171,45174,45200,45204,45207,45221,45223,45226,45229,45233,45270],[863,44509,44511],{"id":44510},"what-is-musicxml","What is MusicXML?",[842,44513,44514],{},"MusicXML is an open standard for exchanging digital sheet music. It represents Western musical notation in XML format. Created by Recordare in 2004, it's now maintained by the W3C Music Notation Community Group and has become the universal language for score exchange.",[842,44516,44517],{},"Unlike proprietary Finale or Sibelius files, MusicXML is open and well-documented. Any software vendor can implement support, making cross-application data exchange straightforward.",[863,44519,44521],{"id":44520},"musicxml-file-structure","MusicXML File Structure",[842,44523,44524],{},"A MusicXML file consists of a hierarchical structure of XML elements describing all aspects of a musical score:",[958,44526,44527,44540,44548,44556,44564,44572],{},[961,44528,44529,28366,44534,44539],{},[996,44530,44531],{},[895,44532,44533],{},"\u003Cscore-partwise>",[996,44535,44536],{},[895,44537,44538],{},"\u003Cscore-timewise>"," - the root element determining how data is organized (by part or by measure)",[961,44541,44542,44547],{},[996,44543,44544],{},[895,44545,44546],{},"\u003Cpart-list>"," - list of all instrumental parts in the piece",[961,44549,44550,44555],{},[996,44551,44552],{},[895,44553,44554],{},"\u003Cpart>"," - a single instrumental part",[961,44557,44558,44563],{},[996,44559,44560],{},[895,44561,44562],{},"\u003Cmeasure>"," - a musical measure containing notes and other elements",[961,44565,44566,44571],{},[996,44567,44568],{},[895,44569,44570],{},"\u003Cnote>"," - a single note with attributes such as pitch, rhythmic value, and articulation",[961,44573,44574,44579],{},[996,44575,44576],{},[895,44577,44578],{},"\u003Cattributes>"," - time signature, key signature, clef, and other musical attributes",[842,44581,44582],{},"Example structure of a simple note in MusicXML:",[1013,44584,44586],{"className":11154,"code":44585,"language":11157,"meta":728,"style":728},"\u003Cnote>\n  \u003Cpitch>\n    \u003Cstep>C\u003C/step>\n    \u003Coctave>4\u003C/octave>\n  \u003C/pitch>\n  \u003Cduration>1\u003C/duration>\n  \u003Ctype>quarter\u003C/type>\n\u003C/note>\n",[895,44587,44588,44596,44605,44623,44640,44648,44664,44681],{"__ignoreMap":728},[1086,44589,44590,44592,44594],{"class":1088,"line":1089},[1086,44591,11164],{"class":1146},[1086,44593,1572],{"class":4109},[1086,44595,11170],{"class":1146},[1086,44597,44598,44600,44603],{"class":1088,"line":729},[1086,44599,11180],{"class":1146},[1086,44601,44602],{"class":4109},"pitch",[1086,44604,11170],{"class":1146},[1086,44606,44607,44609,44612,44614,44617,44619,44621],{"class":1088,"line":1112},[1086,44608,11190],{"class":1146},[1086,44610,44611],{"class":4109},"step",[1086,44613,2694],{"class":1146},[1086,44615,44616],{"class":1436},"C",[1086,44618,11201],{"class":1146},[1086,44620,44611],{"class":4109},[1086,44622,11170],{"class":1146},[1086,44624,44625,44627,44630,44632,44634,44636,44638],{"class":1088,"line":1181},[1086,44626,11190],{"class":1146},[1086,44628,44629],{"class":4109},"octave",[1086,44631,2694],{"class":1146},[1086,44633,7989],{"class":1436},[1086,44635,11201],{"class":1146},[1086,44637,44629],{"class":4109},[1086,44639,11170],{"class":1146},[1086,44641,44642,44644,44646],{"class":1088,"line":1205},[1086,44643,11260],{"class":1146},[1086,44645,44602],{"class":4109},[1086,44647,11170],{"class":1146},[1086,44649,44650,44652,44654,44656,44658,44660,44662],{"class":1088,"line":1276},[1086,44651,11180],{"class":1146},[1086,44653,30984],{"class":4109},[1086,44655,2694],{"class":1146},[1086,44657,4434],{"class":1436},[1086,44659,11201],{"class":1146},[1086,44661,30984],{"class":4109},[1086,44663,11170],{"class":1146},[1086,44665,44666,44668,44670,44672,44675,44677,44679],{"class":1088,"line":1282},[1086,44667,11180],{"class":1146},[1086,44669,12011],{"class":4109},[1086,44671,2694],{"class":1146},[1086,44673,44674],{"class":1436},"quarter",[1086,44676,11201],{"class":1146},[1086,44678,12011],{"class":4109},[1086,44680,11170],{"class":1146},[1086,44682,44683,44685,44687],{"class":1088,"line":1288},[1086,44684,11201],{"class":1146},[1086,44686,1572],{"class":4109},[1086,44688,11170],{"class":1146},[863,44690,44692],{"id":44691},"applications-in-the-music-industry","Applications in the Music Industry",[1074,44694,44696],{"id":44695},"score-exchange-between-applications","Score Exchange Between Applications",[842,44698,44699],{},"The primary use of MusicXML is transferring scores between different music notation applications. A musician working in Finale can export their composition to MusicXML and send it to a collaborator using Sibelius or MuseScore. This interoperability is crucial in professional music production, where different teams often use different tools.",[1074,44701,44703],{"id":44702},"archiving-and-digitization","Archiving and Digitization",[842,44705,44706],{},"Music libraries and archives increasingly use MusicXML for digitizing historical scores. This format allows storage of not only the visual representation of notes but also semantic information about the piece's structure, enabling advanced searching and analysis.",[1074,44708,44710],{"id":44709},"automatic-arrangement-and-transposition","Automatic Arrangement and Transposition",[842,44712,44713],{},"Programs using MusicXML can automatically transpose pieces to different keys or generate arrangements for various instrumental ensembles. The semantic structure of the format makes such operations much simpler than with graphic formats.",[1074,44715,44717],{"id":44716},"integration-with-daws-and-synthesizers","Integration with DAWs and Synthesizers",[842,44719,44720],{},"Some digital audio workstations (DAWs) support MusicXML import, allowing conversion of traditional notation to MIDI tracks. This is particularly useful for composers combining traditional notation with electronic production.",[863,44722,44724],{"id":44723},"musicxml-in-music-education","MusicXML in Music Education",[1074,44726,44728],{"id":44727},"interactive-educational-materials","Interactive Educational Materials",[842,44730,44731],{},"Music educators use MusicXML to create interactive exercises. Students can:",[958,44733,44734,44737,44740,44743],{},[961,44735,44736],{},"Play back scores at different tempos",[961,44738,44739],{},"Listen to selected parts separately",[961,44741,44742],{},"Follow the notation during playback",[961,44744,44745],{},"Modify and experiment with musical material",[1074,44747,44749],{"id":44748},"instrument-learning-applications","Instrument Learning Applications",[842,44751,44752],{},"Many educational applications import MusicXML files to present students with notation synchronized to audio backing. Applications like SmartMusic, Yousician, and Piano Marvel use this format to deliver interactive lessons.",[1074,44754,44756],{"id":44755},"accessibility-for-blind-musicians","Accessibility for Blind Musicians",[842,44758,44759],{},"MusicXML plays an important role in creating accessible musical materials. The format can be converted to music braille notation, enabling blind and visually impaired people to access musical scores. Projects like FreeDots and Music21 offer tools for such conversion.",[1074,44761,44763],{"id":44762},"music-analysis-and-theory","Music Analysis and Theory",[842,44765,44766],{},"Musicology students use MusicXML in combination with programming libraries (e.g., music21 in Python) for:",[958,44768,44769,44772,44775,44778],{},[961,44770,44771],{},"Harmonic analysis of compositions",[961,44773,44774],{},"Studying statistical patterns in compositions",[961,44776,44777],{},"Comparing different versions of the same piece",[961,44779,44780],{},"Visualizing musical structures",[863,44782,44784],{"id":44783},"programming-with-musicxml","Programming with MusicXML",[1074,44786,44788],{"id":44787},"python-and-the-music21-library","Python and the music21 Library",[842,44790,44791],{},"The most popular tool for working with MusicXML in Python is the music21 library developed by MIT. It enables:",[1013,44793,44795],{"className":1368,"code":44794,"language":1250,"meta":728,"style":728},"from music21 import converter, analysis\n\n# Load MusicXML file\nscore = converter.parse('composition.musicxml')\n\n# Key analysis\nkey = score.analyze('key')\nprint(f\"Key: {key}\")\n\n# Transpose up a minor third\ntransposed = score.transpose('m3')\ntransposed.write('musicxml', 'composition_transposed.musicxml')\n",[895,44796,44797,44814,44818,44823,44847,44851,44856,44880,44901,44905,44910,44935],{"__ignoreMap":728},[1086,44798,44799,44801,44804,44806,44809,44811],{"class":1088,"line":1089},[1086,44800,15570],{"class":1423},[1086,44802,44803],{"class":1436}," music21 ",[1086,44805,6503],{"class":1423},[1086,44807,44808],{"class":1436}," converter",[1086,44810,1227],{"class":1146},[1086,44812,44813],{"class":1436}," analysis\n",[1086,44815,44816],{"class":1088,"line":729},[1086,44817,3390],{"emptyLinePlaceholder":738},[1086,44819,44820],{"class":1088,"line":1112},[1086,44821,44822],{"class":1427},"# Load MusicXML file\n",[1086,44824,44825,44828,44830,44832,44834,44836,44838,44840,44843,44845],{"class":1088,"line":1181},[1086,44826,44827],{"class":1436},"score ",[1086,44829,1440],{"class":1146},[1086,44831,44808],{"class":1436},[1086,44833,861],{"class":1146},[1086,44835,33508],{"class":1105},[1086,44837,1398],{"class":1146},[1086,44839,10742],{"class":1146},[1086,44841,44842],{"class":1096},"composition.musicxml",[1086,44844,10742],{"class":1146},[1086,44846,1455],{"class":1146},[1086,44848,44849],{"class":1088,"line":1205},[1086,44850,3390],{"emptyLinePlaceholder":738},[1086,44852,44853],{"class":1088,"line":1276},[1086,44854,44855],{"class":1427},"# Key analysis\n",[1086,44857,44858,44861,44863,44866,44868,44870,44872,44874,44876,44878],{"class":1088,"line":1282},[1086,44859,44860],{"class":1436},"key ",[1086,44862,1440],{"class":1146},[1086,44864,44865],{"class":1436}," score",[1086,44867,861],{"class":1146},[1086,44869,32055],{"class":1105},[1086,44871,1398],{"class":1146},[1086,44873,10742],{"class":1146},[1086,44875,43436],{"class":1096},[1086,44877,10742],{"class":1146},[1086,44879,1455],{"class":1146},[1086,44881,44882,44884,44886,44888,44891,44893,44895,44897,44899],{"class":1088,"line":1288},[1086,44883,10725],{"class":1105},[1086,44885,1398],{"class":1146},[1086,44887,5962],{"class":1155},[1086,44889,44890],{"class":1096},"\"Key: ",[1086,44892,4409],{"class":1187},[1086,44894,43436],{"class":1105},[1086,44896,4423],{"class":1187},[1086,44898,1159],{"class":1096},[1086,44900,1455],{"class":1146},[1086,44902,44903],{"class":1088,"line":2685},[1086,44904,3390],{"emptyLinePlaceholder":738},[1086,44906,44907],{"class":1088,"line":2700},[1086,44908,44909],{"class":1427},"# Transpose up a minor third\n",[1086,44911,44912,44915,44917,44919,44921,44924,44926,44928,44931,44933],{"class":1088,"line":3398},[1086,44913,44914],{"class":1436},"transposed ",[1086,44916,1440],{"class":1146},[1086,44918,44865],{"class":1436},[1086,44920,861],{"class":1146},[1086,44922,44923],{"class":1105},"transpose",[1086,44925,1398],{"class":1146},[1086,44927,10742],{"class":1146},[1086,44929,44930],{"class":1096},"m3",[1086,44932,10742],{"class":1146},[1086,44934,1455],{"class":1146},[1086,44936,44937,44940,44942,44944,44946,44948,44951,44953,44955,44957,44960,44962],{"class":1088,"line":1715},[1086,44938,44939],{"class":1436},"transposed",[1086,44941,861],{"class":1146},[1086,44943,29046],{"class":1105},[1086,44945,1398],{"class":1146},[1086,44947,10742],{"class":1146},[1086,44949,44950],{"class":1096},"musicxml",[1086,44952,10742],{"class":1146},[1086,44954,1227],{"class":1146},[1086,44956,26405],{"class":1146},[1086,44958,44959],{"class":1096},"composition_transposed.musicxml",[1086,44961,10742],{"class":1146},[1086,44963,1455],{"class":1146},[1074,44965,44967],{"id":44966},"javascript-and-the-opensheetmusicdisplay-library","JavaScript and the opensheetmusicdisplay Library",[842,44969,44970],{},"In the web environment, the opensheetmusicdisplay library is popular for rendering MusicXML files directly in the browser:",[1013,44972,44974],{"className":33433,"code":44973,"language":33435,"meta":728,"style":728},"import { OpenSheetMusicDisplay } from 'opensheetmusicdisplay';\n\nconst osmd = new OpenSheetMusicDisplay(\"container\");\nosmd.load(\"composition.musicxml\")\n  .then(() => osmd.render());\n",[895,44975,44976,44998,45002,45028,45048],{"__ignoreMap":728},[1086,44977,44978,44980,44982,44985,44987,44989,44991,44994,44996],{"class":1088,"line":1089},[1086,44979,6503],{"class":1423},[1086,44981,4520],{"class":1146},[1086,44983,44984],{"class":1436}," OpenSheetMusicDisplay",[1086,44986,4690],{"class":1146},[1086,44988,26402],{"class":1423},[1086,44990,26405],{"class":1146},[1086,44992,44993],{"class":1096},"opensheetmusicdisplay",[1086,44995,10742],{"class":1146},[1086,44997,33466],{"class":1146},[1086,44999,45000],{"class":1088,"line":729},[1086,45001,3390],{"emptyLinePlaceholder":738},[1086,45003,45004,45006,45009,45011,45013,45015,45017,45019,45022,45024,45026],{"class":1088,"line":1112},[1086,45005,33442],{"class":1155},[1086,45007,45008],{"class":1436}," osmd ",[1086,45010,1440],{"class":1146},[1086,45012,26591],{"class":1146},[1086,45014,44984],{"class":1105},[1086,45016,1398],{"class":1436},[1086,45018,1159],{"class":1146},[1086,45020,45021],{"class":1096},"container",[1086,45023,1159],{"class":1146},[1086,45025,1410],{"class":1436},[1086,45027,33466],{"class":1146},[1086,45029,45030,45033,45035,45038,45040,45042,45044,45046],{"class":1088,"line":1181},[1086,45031,45032],{"class":1436},"osmd",[1086,45034,861],{"class":1146},[1086,45036,45037],{"class":1105},"load",[1086,45039,1398],{"class":1436},[1086,45041,1159],{"class":1146},[1086,45043,44842],{"class":1096},[1086,45045,1159],{"class":1146},[1086,45047,1455],{"class":1436},[1086,45049,45050,45053,45056,45058,45060,45062,45065,45067,45070,45073],{"class":1088,"line":1205},[1086,45051,45052],{"class":1146},"  .",[1086,45054,45055],{"class":1105},"then",[1086,45057,1398],{"class":1436},[1086,45059,2516],{"class":1146},[1086,45061,26610],{"class":1155},[1086,45063,45064],{"class":1436}," osmd",[1086,45066,861],{"class":1146},[1086,45068,45069],{"class":1105},"render",[1086,45071,45072],{"class":1436},"())",[1086,45074,33466],{"class":1146},[1074,45076,45078],{"id":45077},"xml-validation-and-parsing","XML Validation and Parsing",[842,45080,45081],{},"As an XML-based format, MusicXML can be processed by standard XML libraries in any programming language. Official XSD schemas allow validation of file correctness.",[863,45083,45085],{"id":45084},"comparison-with-other-formats","Comparison with Other Formats",[871,45087,45088,45102],{},[874,45089,45090],{},[877,45091,45092,45094,45096,45099],{},[880,45093,14141],{},[880,45095,8043],{},[880,45097,45098],{},"Openness",[880,45100,45101],{},"Primary Use",[887,45103,45104,45118,45130,45142,45155],{},[877,45105,45106,45109,45112,45115],{},[892,45107,45108],{},"MusicXML",[892,45110,45111],{},"XML",[892,45113,45114],{},"Open",[892,45116,45117],{},"Score exchange",[877,45119,45120,45123,45125,45127],{},[892,45121,45122],{},"MEI",[892,45124,45111],{},[892,45126,45114],{},[892,45128,45129],{},"Musicology, archiving",[877,45131,45132,45135,45137,45139],{},[892,45133,45134],{},"MIDI",[892,45136,30159],{},[892,45138,45114],{},[892,45140,45141],{},"Performance data",[877,45143,45144,45147,45149,45152],{},[892,45145,45146],{},"Finale (.musx)",[892,45148,30159],{},[892,45150,45151],{},"Closed",[892,45153,45154],{},"Editing in Finale",[877,45156,45157,45160,45162,45164],{},[892,45158,45159],{},"Sibelius (.sib)",[892,45161,30159],{},[892,45163,45151],{},[892,45165,45166],{},"Editing in Sibelius",[863,45168,45170],{"id":45169},"musicxml-limitations","MusicXML Limitations",[842,45172,45173],{},"Despite its many advantages, MusicXML has some limitations:",[958,45175,45176,45182,45188,45194],{},[961,45177,45178,45181],{},[996,45179,45180],{},"File size"," - XML files are significantly larger than binary formats",[961,45183,45184,45187],{},[996,45185,45186],{},"Complexity"," - the full specification is extensive and complicated",[961,45189,45190,45193],{},[996,45191,45192],{},"Formatting loss"," - specific visual elements may be lost when converting between programs",[961,45195,45196,45199],{},[996,45197,45198],{},"No sound standardization"," - the format describes notation, not sound (unlike MIDI)",[863,45201,45203],{"id":45202},"the-future-of-musicxml","The Future of MusicXML",[842,45205,45206],{},"The W3C Music Notation Community Group continues to develop the standard. Version 4.0 introduces, among other things:",[958,45208,45209,45212,45215,45218],{},[961,45210,45211],{},"Better support for contemporary and experimental notation",[961,45213,45214],{},"Extended capabilities for describing dynamics and articulation",[961,45216,45217],{},"Improvements in representing non-metric music",[961,45219,45220],{},"Integration with the SMuFL (Standard Music Font Layout) format",[863,45222,25699],{"id":8196},[842,45224,45225],{},"MusicXML forms the foundation of interoperability in digital music notation. For musicians, educators, and developers, it is an invaluable tool enabling the exchange, analysis, and processing of musical scores. As music education digitization grows and music applications develop, the importance of this standard will only increase.",[842,45227,45228],{},"For technical teams working on music projects, knowledge of MusicXML is a key competency for building solutions compatible with the broad ecosystem of music tools.",[863,45230,45232],{"id":45231},"useful-resources","Useful Resources",[958,45234,45235,45242,45249,45256,45263],{},[961,45236,45237],{},[846,45238,45241],{"href":45239,"rel":45240},"https://www.musicxml.com/",[850],"Official MusicXML Website",[961,45243,45244],{},[846,45245,45248],{"href":45246,"rel":45247},"https://www.w3.org/community/music-notation/",[850],"W3C Music Notation Community Group",[961,45250,45251],{},[846,45252,45255],{"href":45253,"rel":45254},"https://web.mit.edu/music21/",[850],"music21 Documentation",[961,45257,45258],{},[846,45259,45262],{"href":45260,"rel":45261},"https://opensheetmusicdisplay.org/",[850],"OpenSheetMusicDisplay",[961,45264,45265],{},[846,45266,45269],{"href":45267,"rel":45268},"https://www.w3.org/2021/06/musicxml40/",[850],"MusicXML 4.0 Specification",[1680,45271,45272],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":728,"searchDepth":729,"depth":729,"links":45274},[45275,45276,45277,45283,45289,45294,45295,45296,45297,45298],{"id":44510,"depth":729,"text":44511},{"id":44520,"depth":729,"text":44521},{"id":44691,"depth":729,"text":44692,"children":45278},[45279,45280,45281,45282],{"id":44695,"depth":1112,"text":44696},{"id":44702,"depth":1112,"text":44703},{"id":44709,"depth":1112,"text":44710},{"id":44716,"depth":1112,"text":44717},{"id":44723,"depth":729,"text":44724,"children":45284},[45285,45286,45287,45288],{"id":44727,"depth":1112,"text":44728},{"id":44748,"depth":1112,"text":44749},{"id":44755,"depth":1112,"text":44756},{"id":44762,"depth":1112,"text":44763},{"id":44783,"depth":729,"text":44784,"children":45290},[45291,45292,45293],{"id":44787,"depth":1112,"text":44788},{"id":44966,"depth":1112,"text":44967},{"id":45077,"depth":1112,"text":45078},{"id":45084,"depth":729,"text":45085},{"id":45169,"depth":729,"text":45170},{"id":45202,"depth":729,"text":45203},{"id":8196,"depth":729,"text":25699},{"id":45231,"depth":729,"text":45232},"2026-01-15T00:00:00.000Z","What is MusicXML, how does it work, and what are its applications in music and education? A guide to the open standard for sheet music exchange.",{"src":45302},"/images/blog/musictechlab_blog_musicxml-standard.webp",{"enabled":738,"items":45304},[45305,45307,45309,45311],{"text":45306,"icon":9547},"MusicXML is the universal open standard for exchanging digital sheet music across applications.",{"text":45308,"icon":5365},"Python's music21 library enables key analysis, transposition, and statistical study of MusicXML files.",{"text":45310,"icon":4845},"MusicXML can be converted to music braille, making scores accessible to blind musicians.",{"text":45312,"icon":1769},"Version 4.0 adds better support for contemporary notation and SMuFL font integration.",{},{"title":586,"description":45300},[5523,731],"RMUBgJBMMiRalXu39PcOF_a2vQZmUD88k-VCZ_gaBUQ",{"id":45318,"title":438,"authors":45319,"badge":45322,"body":45325,"category":756,"client":723,"date":46458,"description":46459,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":46460,"keyTakeaways":46462,"meta":46470,"navigation":738,"path":439,"seo":46471,"status":723,"stem":440,"tags":46472,"teaser":723,"__hash__":46473},"posts/blog/software-development/did-you-know-dev-tips-part-1.md",[45320],{"name":834,"to":720,"avatar":45321},{"src":722},{"label":45323,"color":45324},"Series","#7C3AED",{"type":725,"value":45326,"toc":46444},[45327,45330,45332,45336,45342,45352,45504,45507,45509,45513,45518,45523,45542,45545,45547,45551,45556,45561,45600,45603,45605,45609,45617,45631,45747,45750,45752,45756,45761,45766,45780,45783,45785,45789,45794,45803,45912,45915,45917,45921,45926,45931,45996,46007,46009,46013,46018,46023,46116,46119,46121,46125,46130,46135,46237,46243,46245,46249,46254,46259,46300,46303,46305,46309,46314,46423,46426,46428,46430,46433,46441],[842,45328,45329],{},"Welcome to \"Did You Know?\" - a series where we share practical tips, clever workarounds, and non-obvious solutions discovered in real production codebases. These aren't theoretical best practices - they're battle-tested patterns from actual projects.",[4937,45331],{},[863,45333,45335],{"id":45334},"_1-codemagic-cant-build-ios-and-macos-simultaneously-but-theres-a-workaround","1. Codemagic Can't Build iOS and macOS Simultaneously (But There's a Workaround)",[842,45337,45338,45341],{},[996,45339,45340],{},"The Problem:"," You have a Flutter app targeting both iOS and macOS. You'd expect to build them in one pipeline, but Codemagic doesn't allow selecting both platforms in a single workflow.",[842,45343,45344,45347,45348,45351],{},[996,45345,45346],{},"Did you know?"," The solution is to use custom ",[895,45349,45350],{},"codemagic.yaml"," files with separate workflows:",[1013,45353,45355],{"className":15447,"code":45354,"language":15449,"meta":728,"style":728},"workflows:\n  ios-build:\n    name: iOS Build\n    instance_type: mac_mini_m2\n    environment:\n      xcode: latest\n    scripts:\n      - flutter build ios --release\n    artifacts:\n      - build/ios/ipa/*.ipa\n\n  macos-build:\n    name: macOS Build\n    instance_type: mac_mini_m2\n    environment:\n      xcode: latest\n    scripts:\n      - flutter build macos --release\n    artifacts:\n      - build/macos/**/*.app\n",[895,45356,45357,45364,45371,45380,45390,45397,45407,45414,45422,45429,45436,45440,45447,45456,45464,45470,45478,45484,45491,45497],{"__ignoreMap":728},[1086,45358,45359,45362],{"class":1088,"line":1089},[1086,45360,45361],{"class":4109},"workflows",[1086,45363,1418],{"class":1146},[1086,45365,45366,45369],{"class":1088,"line":729},[1086,45367,45368],{"class":4109},"  ios-build",[1086,45370,1418],{"class":1146},[1086,45372,45373,45375,45377],{"class":1088,"line":1112},[1086,45374,38386],{"class":4109},[1086,45376,1133],{"class":1146},[1086,45378,45379],{"class":1096}," iOS Build\n",[1086,45381,45382,45385,45387],{"class":1088,"line":1181},[1086,45383,45384],{"class":4109},"    instance_type",[1086,45386,1133],{"class":1146},[1086,45388,45389],{"class":1096}," mac_mini_m2\n",[1086,45391,45392,45395],{"class":1088,"line":1205},[1086,45393,45394],{"class":4109},"    environment",[1086,45396,1418],{"class":1146},[1086,45398,45399,45402,45404],{"class":1088,"line":1276},[1086,45400,45401],{"class":4109},"      xcode",[1086,45403,1133],{"class":1146},[1086,45405,45406],{"class":1096}," latest\n",[1086,45408,45409,45412],{"class":1088,"line":1282},[1086,45410,45411],{"class":4109},"    scripts",[1086,45413,1418],{"class":1146},[1086,45415,45416,45419],{"class":1088,"line":1288},[1086,45417,45418],{"class":1146},"      -",[1086,45420,45421],{"class":1096}," flutter build ios --release\n",[1086,45423,45424,45427],{"class":1088,"line":2685},[1086,45425,45426],{"class":4109},"    artifacts",[1086,45428,1418],{"class":1146},[1086,45430,45431,45433],{"class":1088,"line":2700},[1086,45432,45418],{"class":1146},[1086,45434,45435],{"class":1096}," build/ios/ipa/*.ipa\n",[1086,45437,45438],{"class":1088,"line":3398},[1086,45439,3390],{"emptyLinePlaceholder":738},[1086,45441,45442,45445],{"class":1088,"line":1715},[1086,45443,45444],{"class":4109},"  macos-build",[1086,45446,1418],{"class":1146},[1086,45448,45449,45451,45453],{"class":1088,"line":3409},[1086,45450,38386],{"class":4109},[1086,45452,1133],{"class":1146},[1086,45454,45455],{"class":1096}," macOS Build\n",[1086,45457,45458,45460,45462],{"class":1088,"line":3415},[1086,45459,45384],{"class":4109},[1086,45461,1133],{"class":1146},[1086,45463,45389],{"class":1096},[1086,45465,45466,45468],{"class":1088,"line":3421},[1086,45467,45394],{"class":4109},[1086,45469,1418],{"class":1146},[1086,45471,45472,45474,45476],{"class":1088,"line":3427},[1086,45473,45401],{"class":4109},[1086,45475,1133],{"class":1146},[1086,45477,45406],{"class":1096},[1086,45479,45480,45482],{"class":1088,"line":3433},[1086,45481,45411],{"class":4109},[1086,45483,1418],{"class":1146},[1086,45485,45486,45488],{"class":1088,"line":3439},[1086,45487,45418],{"class":1146},[1086,45489,45490],{"class":1096}," flutter build macos --release\n",[1086,45492,45493,45495],{"class":1088,"line":3444},[1086,45494,45426],{"class":4109},[1086,45496,1418],{"class":1146},[1086,45498,45499,45501],{"class":1088,"line":3450},[1086,45500,45418],{"class":1146},[1086,45502,45503],{"class":1096}," build/macos/**/*.app\n",[842,45505,45506],{},"You can trigger both workflows in parallel from your CI, or chain them sequentially. The key insight: don't fight the UI limitations - embrace YAML-based configuration for full control.",[4937,45508],{},[863,45510,45512],{"id":45511},"_2-docker-buildkit-cache-mounts-can-cut-build-times-by-80","2. Docker BuildKit Cache Mounts Can Cut Build Times by 80%",[842,45514,45515,45517],{},[996,45516,45340],{}," Every Docker build reinstalls all dependencies from scratch, even if nothing changed.",[842,45519,45520,45522],{},[996,45521,45346],{}," BuildKit cache mounts persist pip and poetry caches across builds:",[1013,45524,45526],{"className":34190,"code":45525,"language":34193,"meta":728,"style":728},"RUN --mount=type=cache,mode=0755,target=/root/.cache/pip pip install poetry==1.8.4\nRUN --mount=type=cache,mode=0755,target=/root/.cache/pypoetry poetry install --no-root\n",[895,45527,45528,45535],{"__ignoreMap":728},[1086,45529,45530,45532],{"class":1088,"line":1089},[1086,45531,34228],{"class":1187},[1086,45533,45534],{"class":1436}," --mount=type=cache,mode=0755,target=/root/.cache/pip pip install poetry==1.8.4\n",[1086,45536,45537,45539],{"class":1088,"line":729},[1086,45538,34228],{"class":1187},[1086,45540,45541],{"class":1436}," --mount=type=cache,mode=0755,target=/root/.cache/pypoetry poetry install --no-root\n",[842,45543,45544],{},"This reuses downloaded packages across builds. A typical Python project with 100+ dependencies goes from 3 minutes to 30 seconds on subsequent builds.",[4937,45546],{},[863,45548,45550],{"id":45549},"_3-one-docker-container-two-behaviors","3. One Docker Container, Two Behaviors",[842,45552,45553,45555],{},[996,45554,45340],{}," You need different startup commands for development (hot reload) vs production (gunicorn), but don't want to maintain separate Dockerfiles.",[842,45557,45558,45560],{},[996,45559,45346],{}," You can switch behavior at runtime using environment variables in your command:",[1013,45562,45564],{"className":15447,"code":45563,"language":15449,"meta":728,"style":728},"command: >\n  sh -c \"if [ \\\"$DEVELOPMENT_MODE\\\" = \\\"True\\\" ]; then\n    poetry run python manage.py runserver 0.0.0.0:8000;\n  else\n    gunicorn --bind :8000 --workers 8 musicdatalab.wsgi:application;\n  fi\"\n",[895,45565,45566,45575,45580,45585,45590,45595],{"__ignoreMap":728},[1086,45567,45568,45570,45572],{"class":1088,"line":1089},[1086,45569,1188],{"class":4109},[1086,45571,1133],{"class":1146},[1086,45573,45574],{"class":1423}," >\n",[1086,45576,45577],{"class":1088,"line":729},[1086,45578,45579],{"class":1096},"  sh -c \"if [ \\\"$DEVELOPMENT_MODE\\\" = \\\"True\\\" ]; then\n",[1086,45581,45582],{"class":1088,"line":1112},[1086,45583,45584],{"class":1096},"    poetry run python manage.py runserver 0.0.0.0:8000;\n",[1086,45586,45587],{"class":1088,"line":1181},[1086,45588,45589],{"class":1096},"  else\n",[1086,45591,45592],{"class":1088,"line":1205},[1086,45593,45594],{"class":1096},"    gunicorn --bind :8000 --workers 8 musicdatalab.wsgi:application;\n",[1086,45596,45597],{"class":1088,"line":1276},[1086,45598,45599],{"class":1096},"  fi\"\n",[842,45601,45602],{},"Same image, different modes. Deploy once, configure per environment.",[4937,45604],{},[863,45606,45608],{"id":45607},"_4-health-checks-prevent-race-conditions-in-docker-compose","4. Health Checks Prevent Race Conditions in Docker Compose",[842,45610,45611,45613,45614,861],{},[996,45612,45340],{}," Your app crashes on startup because the database isn't ready yet, even though you used ",[895,45615,45616],{},"depends_on",[842,45618,45619,7826,45621,45623,45624,45626,45627,45630],{},[996,45620,45346],{},[895,45622,45616],{}," only waits for the container to ",[964,45625,29437],{},", not for the service to be ",[964,45628,45629],{},"ready",". Use health checks:",[1013,45632,45634],{"className":15447,"code":45633,"language":15449,"meta":728,"style":728},"depends_on:\n  db:\n    condition: service_healthy\n  redis:\n    condition: service_started\n\n# In the db service:\nhealthcheck:\n  test: [\"CMD-SHELL\", \"pg_isready -U myuser\"]\n  interval: 2s\n  timeout: 2s\n  retries: 10\n",[895,45635,45636,45642,45649,45659,45666,45675,45679,45684,45691,45718,45728,45737],{"__ignoreMap":728},[1086,45637,45638,45640],{"class":1088,"line":1089},[1086,45639,45616],{"class":4109},[1086,45641,1418],{"class":1146},[1086,45643,45644,45647],{"class":1088,"line":729},[1086,45645,45646],{"class":4109},"  db",[1086,45648,1418],{"class":1146},[1086,45650,45651,45654,45656],{"class":1088,"line":1112},[1086,45652,45653],{"class":4109},"    condition",[1086,45655,1133],{"class":1146},[1086,45657,45658],{"class":1096}," service_healthy\n",[1086,45660,45661,45664],{"class":1088,"line":1181},[1086,45662,45663],{"class":4109},"  redis",[1086,45665,1418],{"class":1146},[1086,45667,45668,45670,45672],{"class":1088,"line":1205},[1086,45669,45653],{"class":4109},[1086,45671,1133],{"class":1146},[1086,45673,45674],{"class":1096}," service_started\n",[1086,45676,45677],{"class":1088,"line":1276},[1086,45678,3390],{"emptyLinePlaceholder":738},[1086,45680,45681],{"class":1088,"line":1282},[1086,45682,45683],{"class":1427},"# In the db service:\n",[1086,45685,45686,45689],{"class":1088,"line":1288},[1086,45687,45688],{"class":4109},"healthcheck",[1086,45690,1418],{"class":1146},[1086,45692,45693,45696,45698,45700,45702,45705,45707,45709,45711,45714,45716],{"class":1088,"line":2685},[1086,45694,45695],{"class":4109},"  test",[1086,45697,1133],{"class":1146},[1086,45699,1217],{"class":1146},[1086,45701,1159],{"class":1146},[1086,45703,45704],{"class":1096},"CMD-SHELL",[1086,45706,1159],{"class":1146},[1086,45708,1227],{"class":1146},[1086,45710,1195],{"class":1146},[1086,45712,45713],{"class":1096},"pg_isready -U myuser",[1086,45715,1159],{"class":1146},[1086,45717,1273],{"class":1146},[1086,45719,45720,45723,45725],{"class":1088,"line":2700},[1086,45721,45722],{"class":4109},"  interval",[1086,45724,1133],{"class":1146},[1086,45726,45727],{"class":1096}," 2s\n",[1086,45729,45730,45733,45735],{"class":1088,"line":3398},[1086,45731,45732],{"class":4109},"  timeout",[1086,45734,1133],{"class":1146},[1086,45736,45727],{"class":1096},[1086,45738,45739,45742,45744],{"class":1088,"line":1715},[1086,45740,45741],{"class":4109},"  retries",[1086,45743,1133],{"class":1146},[1086,45745,45746],{"class":1187}," 10\n",[842,45748,45749],{},"Now Docker waits until PostgreSQL actually accepts connections.",[4937,45751],{},[863,45753,45755],{"id":45754},"_5-celery-workers-can-auto-restart-on-memory-leaks","5. Celery Workers Can Auto-Restart on Memory Leaks",[842,45757,45758,45760],{},[996,45759,45340],{}," Your Celery workers slowly consume more memory until they get OOM-killed, taking running tasks with them.",[842,45762,45763,45765],{},[996,45764,45346],{}," Celery has a built-in flag to gracefully restart workers that exceed a memory threshold:",[1013,45767,45769],{"className":15447,"code":45768,"language":15449,"meta":728,"style":728},"command: celery -A myapp worker --loglevel=info --concurrency=4 --max-memory-per-child=100000\n",[895,45770,45771],{"__ignoreMap":728},[1086,45772,45773,45775,45777],{"class":1088,"line":1089},[1086,45774,1188],{"class":4109},[1086,45776,1133],{"class":1146},[1086,45778,45779],{"class":1096}," celery -A myapp worker --loglevel=info --concurrency=4 --max-memory-per-child=100000\n",[842,45781,45782],{},"Workers restart themselves after processing tasks if they exceed ~100MB. Memory leaks become a minor nuisance instead of a production incident.",[4937,45784],{},[863,45786,45788],{"id":45787},"_6-firebases-immutable-cache-headers-give-you-free-cdn-performance","6. Firebase's Immutable Cache Headers Give You Free CDN Performance",[842,45790,45791,45793],{},[996,45792,45340],{}," Your static assets (images, fonts, JS bundles) get re-downloaded on every visit, wasting bandwidth and slowing load times.",[842,45795,45796,45798,45799,45802],{},[996,45797,45346],{}," If your assets have hashed filenames (like ",[895,45800,45801],{},"app.a1b2c3.js","), you can cache them forever:",[1013,45804,45806],{"className":1136,"code":45805,"language":1139,"meta":728,"style":728},"{\n  \"headers\": [\n    {\n      \"source\": \"**/*.@(jpg|jpeg|gif|png|svg|webp|ico|avif)\",\n      \"headers\": [{\n        \"key\": \"Cache-Control\",\n        \"value\": \"public, max-age=31536000, immutable\"\n      }]\n    }\n  ]\n}\n",[895,45807,45808,45812,45824,45828,45847,45859,45878,45895,45900,45904,45908],{"__ignoreMap":728},[1086,45809,45810],{"class":1088,"line":1089},[1086,45811,1147],{"class":1146},[1086,45813,45814,45816,45818,45820,45822],{"class":1088,"line":729},[1086,45815,1152],{"class":1146},[1086,45817,17277],{"class":1155},[1086,45819,1159],{"class":1146},[1086,45821,1133],{"class":1146},[1086,45823,6580],{"class":1146},[1086,45825,45826],{"class":1088,"line":1112},[1086,45827,9234],{"class":1146},[1086,45829,45830,45832,45834,45836,45838,45840,45843,45845],{"class":1088,"line":1181},[1086,45831,1184],{"class":1146},[1086,45833,27530],{"class":1092},[1086,45835,1159],{"class":1146},[1086,45837,1133],{"class":1146},[1086,45839,1195],{"class":1146},[1086,45841,45842],{"class":1096},"**/*.@(jpg|jpeg|gif|png|svg|webp|ico|avif)",[1086,45844,1159],{"class":1146},[1086,45846,1202],{"class":1146},[1086,45848,45849,45851,45853,45855,45857],{"class":1088,"line":1205},[1086,45850,1184],{"class":1146},[1086,45852,17277],{"class":1092},[1086,45854,1159],{"class":1146},[1086,45856,1133],{"class":1146},[1086,45858,23412],{"class":1146},[1086,45860,45861,45863,45865,45867,45869,45871,45874,45876],{"class":1088,"line":1276},[1086,45862,6046],{"class":1146},[1086,45864,43436],{"class":1187},[1086,45866,1159],{"class":1146},[1086,45868,1133],{"class":1146},[1086,45870,1195],{"class":1146},[1086,45872,45873],{"class":1096},"Cache-Control",[1086,45875,1159],{"class":1146},[1086,45877,1202],{"class":1146},[1086,45879,45880,45882,45884,45886,45888,45890,45893],{"class":1088,"line":1282},[1086,45881,6046],{"class":1146},[1086,45883,26662],{"class":1187},[1086,45885,1159],{"class":1146},[1086,45887,1133],{"class":1146},[1086,45889,1195],{"class":1146},[1086,45891,45892],{"class":1096},"public, max-age=31536000, immutable",[1086,45894,4441],{"class":1146},[1086,45896,45897],{"class":1088,"line":1288},[1086,45898,45899],{"class":1146},"      }]\n",[1086,45901,45902],{"class":1088,"line":2685},[1086,45903,1279],{"class":1146},[1086,45905,45906],{"class":1088,"line":2700},[1086,45907,9465],{"class":1146},[1086,45909,45910],{"class":1088,"line":3398},[1086,45911,1291],{"class":1146},[842,45913,45914],{},"One year cache, marked immutable. Browsers won't even send conditional requests. Combined with Nuxt or Next.js hash-based filenames, cache invalidation happens automatically on deploy.",[4937,45916],{},[863,45918,45920],{"id":45919},"_7-pre-commit-hooks-can-enforce-conventional-commits","7. Pre-commit Hooks Can Enforce Conventional Commits",[842,45922,45923,45925],{},[996,45924,45340],{}," Your git history is a mess of \"fix\", \"update\", and \"wip\" messages. You want semantic versioning but can't parse the commits.",[842,45927,45928,45930],{},[996,45929,45346],{}," You can reject non-conventional commits before they're even created:",[1013,45932,45934],{"className":15447,"code":45933,"language":15449,"meta":728,"style":728},"# .pre-commit-config.yaml\n- repo: https://github.com/compilerla/conventional-pre-commit\n  rev: v3.0.0\n  hooks:\n    - id: conventional-pre-commit\n      stages: [commit-msg]\n",[895,45935,45936,45941,45953,45963,45970,45982],{"__ignoreMap":728},[1086,45937,45938],{"class":1088,"line":1089},[1086,45939,45940],{"class":1427},"# .pre-commit-config.yaml\n",[1086,45942,45943,45945,45948,45950],{"class":1088,"line":729},[1086,45944,2635],{"class":1146},[1086,45946,45947],{"class":4109}," repo",[1086,45949,1133],{"class":1146},[1086,45951,45952],{"class":1096}," https://github.com/compilerla/conventional-pre-commit\n",[1086,45954,45955,45958,45960],{"class":1088,"line":1112},[1086,45956,45957],{"class":4109},"  rev",[1086,45959,1133],{"class":1146},[1086,45961,45962],{"class":1096}," v3.0.0\n",[1086,45964,45965,45968],{"class":1088,"line":1181},[1086,45966,45967],{"class":4109},"  hooks",[1086,45969,1418],{"class":1146},[1086,45971,45972,45975,45977,45979],{"class":1088,"line":1205},[1086,45973,45974],{"class":1146},"    -",[1086,45976,41997],{"class":4109},[1086,45978,1133],{"class":1146},[1086,45980,45981],{"class":1096}," conventional-pre-commit\n",[1086,45983,45984,45987,45989,45991,45994],{"class":1088,"line":1276},[1086,45985,45986],{"class":4109},"      stages",[1086,45988,1133],{"class":1146},[1086,45990,1217],{"class":1146},[1086,45992,45993],{"class":1096},"commit-msg",[1086,45995,1273],{"class":1146},[842,45997,45998,45999,46002,46003,46006],{},"Now ",[895,46000,46001],{},"git commit -m \"fixed stuff\""," fails, but ",[895,46004,46005],{},"git commit -m \"fix: resolve login timeout issue\""," succeeds. Your changelog writes itself.",[4937,46008],{},[863,46010,46012],{"id":46011},"_8-shell-scripts-can-fail-fast-with-dependency-checks","8. Shell Scripts Can Fail Fast with Dependency Checks",[842,46014,46015,46017],{},[996,46016,45340],{}," Your startup script fails midway through because a required Python module is missing, leaving the system in a partial state.",[842,46019,46020,46022],{},[996,46021,45346],{}," You can validate critical imports before starting:",[1013,46024,46026],{"className":1080,"code":46025,"language":1082,"meta":728,"style":728},"#!/usr/bin/env sh\nset -eu\n\n# Fail-fast import check\npython - \u003C\u003C'PY'\nimport importlib\nfor m in (\"fastapi\", \"uvicorn\", \"sqlalchemy\"):\n    importlib.import_module(m)\nPY\n\nexec python -m uvicorn app.main:app --host 0.0.0.0 --port \"$PORT\"\n",[895,46027,46028,46033,46040,46044,46049,46061,46066,46071,46076,46081,46085],{"__ignoreMap":728},[1086,46029,46030],{"class":1088,"line":1089},[1086,46031,46032],{"class":1427},"#!/usr/bin/env sh\n",[1086,46034,46035,46037],{"class":1088,"line":729},[1086,46036,20205],{"class":1105},[1086,46038,46039],{"class":1096}," -eu\n",[1086,46041,46042],{"class":1088,"line":1112},[1086,46043,3390],{"emptyLinePlaceholder":738},[1086,46045,46046],{"class":1088,"line":1181},[1086,46047,46048],{"class":1427},"# Fail-fast import check\n",[1086,46050,46051,46053,46055,46058],{"class":1088,"line":1205},[1086,46052,1250],{"class":1092},[1086,46054,2816],{"class":1096},[1086,46056,46057],{"class":1146}," \u003C\u003C",[1086,46059,46060],{"class":1146},"'PY'\n",[1086,46062,46063],{"class":1088,"line":1276},[1086,46064,46065],{"class":1096},"import importlib\n",[1086,46067,46068],{"class":1088,"line":1282},[1086,46069,46070],{"class":1096},"for m in (\"fastapi\", \"uvicorn\", \"sqlalchemy\"):\n",[1086,46072,46073],{"class":1088,"line":1288},[1086,46074,46075],{"class":1096},"    importlib.import_module(m)\n",[1086,46077,46078],{"class":1088,"line":2685},[1086,46079,46080],{"class":1146},"PY\n",[1086,46082,46083],{"class":1088,"line":2700},[1086,46084,3390],{"emptyLinePlaceholder":738},[1086,46086,46087,46090,46092,46094,46097,46100,46103,46106,46109,46111,46114],{"class":1088,"line":3398},[1086,46088,46089],{"class":1105},"exec",[1086,46091,11973],{"class":1096},[1086,46093,11976],{"class":1096},[1086,46095,46096],{"class":1096}," uvicorn",[1086,46098,46099],{"class":1096}," app.main:app",[1086,46101,46102],{"class":1096}," --host",[1086,46104,46105],{"class":1187}," 0.0.0.0",[1086,46107,46108],{"class":1096}," --port",[1086,46110,1195],{"class":1146},[1086,46112,46113],{"class":1436},"$PORT",[1086,46115,4441],{"class":1146},[842,46117,46118],{},"If any import fails, the script exits immediately with a clear error. No partial startups.",[4937,46120],{},[863,46122,46124],{"id":46123},"_9-nuxt-can-auto-discover-routes-by-crawling-links","9. Nuxt Can Auto-Discover Routes by Crawling Links",[842,46126,46127,46129],{},[996,46128,45340],{}," You're using Nuxt's static generation but have to manually list every route for prerendering.",[842,46131,46132,46134],{},[996,46133,45346],{}," Nuxt can automatically discover pages by following internal links:",[1013,46136,46138],{"className":4602,"code":46137,"language":4605,"meta":728,"style":728},"// nuxt.config.ts\nexport default defineNuxtConfig({\n  nitro: {\n    preset: 'static',\n    prerender: {\n      routes: ['/'],\n      crawlLinks: true  // Magic happens here\n    }\n  }\n})\n",[895,46139,46140,46145,46158,46167,46183,46192,46211,46223,46227,46231],{"__ignoreMap":728},[1086,46141,46142],{"class":1088,"line":1089},[1086,46143,46144],{"class":1427},"// nuxt.config.ts\n",[1086,46146,46147,46149,46151,46154,46156],{"class":1088,"line":729},[1086,46148,3625],{"class":1423},[1086,46150,19130],{"class":1423},[1086,46152,46153],{"class":1105}," defineNuxtConfig",[1086,46155,1398],{"class":1436},[1086,46157,1147],{"class":1146},[1086,46159,46160,46163,46165],{"class":1088,"line":1112},[1086,46161,46162],{"class":4109},"  nitro",[1086,46164,1133],{"class":1146},[1086,46166,1164],{"class":1146},[1086,46168,46169,46172,46174,46176,46179,46181],{"class":1088,"line":1181},[1086,46170,46171],{"class":4109},"    preset",[1086,46173,1133],{"class":1146},[1086,46175,26405],{"class":1146},[1086,46177,46178],{"class":1096},"static",[1086,46180,10742],{"class":1146},[1086,46182,1202],{"class":1146},[1086,46184,46185,46188,46190],{"class":1088,"line":1205},[1086,46186,46187],{"class":4109},"    prerender",[1086,46189,1133],{"class":1146},[1086,46191,1164],{"class":1146},[1086,46193,46194,46197,46199,46201,46203,46205,46207,46209],{"class":1088,"line":1276},[1086,46195,46196],{"class":4109},"      routes",[1086,46198,1133],{"class":1146},[1086,46200,1217],{"class":1436},[1086,46202,10742],{"class":1146},[1086,46204,23036],{"class":1096},[1086,46206,10742],{"class":1146},[1086,46208,4420],{"class":1436},[1086,46210,1202],{"class":1146},[1086,46212,46213,46216,46218,46220],{"class":1088,"line":1282},[1086,46214,46215],{"class":4109},"      crawlLinks",[1086,46217,1133],{"class":1146},[1086,46219,27022],{"class":27021},[1086,46221,46222],{"class":1427},"  // Magic happens here\n",[1086,46224,46225],{"class":1088,"line":1288},[1086,46226,1279],{"class":1146},[1086,46228,46229],{"class":1088,"line":2685},[1086,46230,1285],{"class":1146},[1086,46232,46233,46235],{"class":1088,"line":2700},[1086,46234,4423],{"class":1146},[1086,46236,1455],{"class":1436},[842,46238,46239,46240,46242],{},"Start from ",[895,46241,23036],{},", and Nuxt will find and prerender every linked page. Add new content, link to it from anywhere, and it's automatically included in the build.",[4937,46244],{},[863,46246,46248],{"id":46247},"_10-aws-in-docker-disable-ec2-metadata-to-force-environment-variables","10. AWS in Docker: Disable EC2 Metadata to Force Environment Variables",[842,46250,46251,46253],{},[996,46252,45340],{}," Your containerized app ignores the AWS credentials you set in environment variables and tries to use EC2 metadata (which doesn't exist outside EC2).",[842,46255,46256,46258],{},[996,46257,45346],{}," You can explicitly disable the metadata service:",[1013,46260,46262],{"className":15447,"code":46261,"language":15449,"meta":728,"style":728},"environment:\n  - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}\n  - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}\n  - AWS_EC2_METADATA_DISABLED=true\n  - AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=\n",[895,46263,46264,46271,46279,46286,46293],{"__ignoreMap":728},[1086,46265,46266,46269],{"class":1088,"line":1089},[1086,46267,46268],{"class":4109},"environment",[1086,46270,1418],{"class":1146},[1086,46272,46273,46276],{"class":1088,"line":729},[1086,46274,46275],{"class":1146},"  -",[1086,46277,46278],{"class":1096}," AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}\n",[1086,46280,46281,46283],{"class":1088,"line":1112},[1086,46282,46275],{"class":1146},[1086,46284,46285],{"class":1096}," AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}\n",[1086,46287,46288,46290],{"class":1088,"line":1181},[1086,46289,46275],{"class":1146},[1086,46291,46292],{"class":1096}," AWS_EC2_METADATA_DISABLED=true\n",[1086,46294,46295,46297],{"class":1088,"line":1205},[1086,46296,46275],{"class":1146},[1086,46298,46299],{"class":1096}," AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=\n",[842,46301,46302],{},"The AWS SDK has a credential chain that checks metadata before environment variables. Disabling metadata forces it to use your explicit credentials.",[4937,46304],{},[863,46306,46308],{"id":46307},"bonus-ruff-lets-you-apply-different-rules-to-different-paths","Bonus: Ruff Lets You Apply Different Rules to Different Paths",[842,46310,46311,46313],{},[996,46312,45346],{}," You can be strict in production code but lenient in tests:",[1013,46315,46319],{"className":46316,"code":46317,"language":46318,"meta":728,"style":728},"language-toml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# ruff.toml\n[lint.per-file-ignores]\n\"__init__.py\" = [\"E402\"]  # Allow late imports in init files\n\"**/tests/*\" = [\"E402\", \"S101\"]  # Allow assert statements in tests\n\"**/migrations/*\" = [\"E501\"]  # Allow long lines in migrations\n","toml",[895,46320,46321,46326,46340,46365,46398],{"__ignoreMap":728},[1086,46322,46323],{"class":1088,"line":1089},[1086,46324,46325],{"class":1427},"# ruff.toml\n",[1086,46327,46328,46330,46333,46335,46338],{"class":1088,"line":729},[1086,46329,4340],{"class":1146},[1086,46331,46332],{"class":1092},"lint",[1086,46334,861],{"class":1436},[1086,46336,46337],{"class":1092},"per-file-ignores",[1086,46339,1273],{"class":1146},[1086,46341,46342,46344,46347,46349,46351,46353,46355,46358,46360,46362],{"class":1088,"line":1112},[1086,46343,1159],{"class":1146},[1086,46345,46346],{"class":1436},"__init__.py",[1086,46348,1159],{"class":1146},[1086,46350,19552],{"class":1146},[1086,46352,1217],{"class":1146},[1086,46354,1159],{"class":1146},[1086,46356,46357],{"class":1096},"E402",[1086,46359,1159],{"class":1146},[1086,46361,4420],{"class":1146},[1086,46363,46364],{"class":1427},"  # Allow late imports in init files\n",[1086,46366,46367,46369,46372,46374,46376,46378,46380,46382,46384,46386,46388,46391,46393,46395],{"class":1088,"line":1181},[1086,46368,1159],{"class":1146},[1086,46370,46371],{"class":1436},"**/tests/*",[1086,46373,1159],{"class":1146},[1086,46375,19552],{"class":1146},[1086,46377,1217],{"class":1146},[1086,46379,1159],{"class":1146},[1086,46381,46357],{"class":1096},[1086,46383,1159],{"class":1146},[1086,46385,1227],{"class":1146},[1086,46387,1195],{"class":1146},[1086,46389,46390],{"class":1096},"S101",[1086,46392,1159],{"class":1146},[1086,46394,4420],{"class":1146},[1086,46396,46397],{"class":1427},"  # Allow assert statements in tests\n",[1086,46399,46400,46402,46405,46407,46409,46411,46413,46416,46418,46420],{"class":1088,"line":1205},[1086,46401,1159],{"class":1146},[1086,46403,46404],{"class":1436},"**/migrations/*",[1086,46406,1159],{"class":1146},[1086,46408,19552],{"class":1146},[1086,46410,1217],{"class":1146},[1086,46412,1159],{"class":1146},[1086,46414,46415],{"class":1096},"E501",[1086,46417,1159],{"class":1146},[1086,46419,4420],{"class":1146},[1086,46421,46422],{"class":1427},"  # Allow long lines in migrations\n",[842,46424,46425],{},"No more disabling rules globally just because they don't apply everywhere.",[4937,46427],{},[863,46429,31693],{"id":16446},[842,46431,46432],{},"This is Part 1 of an ongoing series. Each installment will bring 10 new tips from real codebases - no fluff, just practical knowledge.",[842,46434,46435,46436,46440],{},"Got a tip of your own? Found something clever in a codebase you're working on? ",[846,46437,46439],{"href":4946,"rel":46438},[850],"Let us know"," - we might feature it in a future edition.",[1680,46442,46443],{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":728,"searchDepth":729,"depth":729,"links":46445},[46446,46447,46448,46449,46450,46451,46452,46453,46454,46455,46456,46457],{"id":45334,"depth":729,"text":45335},{"id":45511,"depth":729,"text":45512},{"id":45549,"depth":729,"text":45550},{"id":45607,"depth":729,"text":45608},{"id":45754,"depth":729,"text":45755},{"id":45787,"depth":729,"text":45788},{"id":45919,"depth":729,"text":45920},{"id":46011,"depth":729,"text":46012},{"id":46123,"depth":729,"text":46124},{"id":46247,"depth":729,"text":46248},{"id":46307,"depth":729,"text":46308},{"id":16446,"depth":729,"text":31693},"2026-01-13T00:00:00.000Z","A collection of practical software development tips, workarounds, and clever solutions discovered in production codebases. Part 1 of an ongoing series.",{"src":46461},"/images/blog/musictechlab_blog_did-you-know-dev-tips.webp",{"enabled":738,"items":46463},[46464,46466,46468],{"text":46465,"icon":2939},"Docker BuildKit cache mounts can cut Python build times from 3 minutes to 30 seconds.",{"text":46467,"icon":4855},"Codemagic requires separate YAML workflows for iOS and macOS builds.",{"text":46469,"icon":12412},"One Docker container can serve dev and production with an environment variable switch.",{},{"title":438,"description":46459},[18784],"QQ_ioW45enKGm65NtMlOp_URCnaPKa-Sww3maRXaZ54",{"id":46475,"title":442,"authors":46476,"badge":46479,"body":46480,"category":756,"client":723,"date":46921,"description":46922,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":46923,"keyTakeaways":46925,"meta":46935,"navigation":738,"path":443,"seo":46936,"status":723,"stem":444,"tags":46937,"teaser":723,"__hash__":46938},"posts/blog/software-development/did-you-know-musictech-facts-part-2.md",[46477],{"name":834,"to":720,"avatar":46478},{"src":722},{"label":45323,"color":45324},{"type":725,"value":46481,"toc":46908},[46482,46485,46487,46491,46496,46499,46502,46508,46510,46514,46519,46522,46528,46530,46534,46539,46565,46568,46570,46574,46579,46582,46606,46609,46611,46615,46620,46623,46626,46628,46632,46637,46643,46657,46660,46662,46666,46671,46674,46680,46682,46686,46691,46694,46708,46711,46713,46717,46722,46725,46730,46744,46746,46750,46755,46772,46775,46880,46882,46884,46887,46893,46895,46905],[842,46483,46484],{},"Welcome to \"Did You Know?\" - MusicTech Edition! A series where we share fascinating facts, surprising innovations, and lesser-known aspects of music technology. From industry standards to accessibility breakthroughs, these are the things that make music technology truly remarkable.",[4937,46486],{},[863,46488,46490],{"id":46489},"_1-musicxml-makes-sheet-music-accessible-to-blind-musicians","1. MusicXML Makes Sheet Music Accessible to Blind Musicians",[842,46492,46493,46495],{},[996,46494,45346],{}," MusicXML, the universal standard for digital sheet music, can be converted to music braille notation, enabling blind and visually impaired musicians to read musical scores independently.",[842,46497,46498],{},"Projects like FreeDots and the music21 Python library provide tools for this conversion. A sighted arranger can create a score in Finale or Sibelius, export it to MusicXML, and a blind musician can convert it to braille notation within minutes.",[842,46500,46501],{},"This wasn't possible before digital standards - blind musicians had to rely on specialized transcribers who would manually convert printed scores to braille, a process that could take weeks for a single symphony.",[842,46503,46504,46507],{},[996,46505,46506],{},"Impact:"," Over 2 million blind and visually impaired people worldwide can now access the same musical scores as sighted musicians, opening doors to music education and professional performance that were previously closed.",[4937,46509],{},[863,46511,46513],{"id":46512},"_2-midi-is-over-40-years-old-and-still-industry-standard","2. MIDI Is Over 40 Years Old and Still Industry Standard",[842,46515,46516,46518],{},[996,46517,45346],{}," The MIDI (Musical Instrument Digital Interface) specification was released in 1983 and remains virtually unchanged as the backbone of music production.",[842,46520,46521],{},"Created through unprecedented cooperation between competing companies (Roland, Sequential Circuits, Oberheim, and others), MIDI was designed to let synthesizers from different manufacturers communicate. Four decades later, it still connects instruments, controllers, and software worldwide.",[842,46523,46524,46527],{},[996,46525,46526],{},"Fun fact:"," MIDI files are incredibly small - Beethoven's entire 9th Symphony as MIDI is about 100KB. The same recording as audio would be 700MB.",[4937,46529],{},[863,46531,46533],{"id":46532},"_3-spotify-analyzes-every-songs-danceability","3. Spotify Analyzes Every Song's \"Danceability\"",[842,46535,46536,46538],{},[996,46537,45346],{}," Spotify calculates audio features for every single track in its library, including:",[958,46540,46541,46547,46553,46559],{},[961,46542,46543,46546],{},[996,46544,46545],{},"Danceability"," (0.0 to 1.0) - How suitable for dancing based on tempo, rhythm stability, and beat strength",[961,46548,46549,46552],{},[996,46550,46551],{},"Valence"," (0.0 to 1.0) - Musical positiveness (high valence = happy, cheerful)",[961,46554,46555,46558],{},[996,46556,46557],{},"Energy"," (0.0 to 1.0) - Perceptual intensity and activity",[961,46560,46561,46564],{},[996,46562,46563],{},"Speechiness"," (0.0 to 1.0) - Presence of spoken words",[842,46566,46567],{},"These features are publicly available through Spotify's Web API, enabling developers to create playlists based on mood, energy levels, or even heart rate from fitness trackers.",[4937,46569],{},[863,46571,46573],{"id":46572},"_4-the-music-industry-has-its-own-language-for-data-exchange","4. The Music Industry Has Its Own \"Language\" for Data Exchange",[842,46575,46576,46578],{},[996,46577,45346],{}," DDEX (Digital Data Exchange) is a consortium that creates standards for how music companies communicate. When you release a song on Spotify, Apple Music, or any major platform, DDEX XML files carry the metadata.",[842,46580,46581],{},"There are different DDEX standards for different purposes:",[958,46583,46584,46589,46594,46600],{},[961,46585,46586,46588],{},[996,46587,9881],{}," (Electronic Release Notification) - For releasing new music",[961,46590,46591,46593],{},[996,46592,9914],{}," (Digital Sales Reporting) - For royalty statements",[961,46595,46596,46599],{},[996,46597,46598],{},"RIN"," (Recording Information Notification) - For studio session data",[961,46601,46602,46605],{},[996,46603,46604],{},"MLC"," (Musical Works Licensing) - For publishing rights",[842,46607,46608],{},"Without DDEX, every platform would speak a different \"language,\" making global music distribution a nightmare.",[4937,46610],{},[863,46612,46614],{"id":46613},"_5-your-favorite-songs-key-was-probably-detected-by-ai","5. Your Favorite Song's Key Was Probably Detected by AI",[842,46616,46617,46619],{},[996,46618,45346],{}," Services like Beatport, rekordbox, and Mixed In Key use machine learning algorithms to detect the musical key of songs with over 95% accuracy.",[842,46621,46622],{},"This technology analyzes the audio's frequency spectrum, identifies the dominant notes, and matches them against key profiles. DJs use this to create harmonically compatible playlists - mixing songs in related keys creates smoother transitions.",[842,46624,46625],{},"The algorithms have become so accurate that they often outperform trained musicians in blind tests, especially for songs with ambiguous tonality.",[4937,46627],{},[863,46629,46631],{"id":46630},"_6-isrc-codes-have-tracked-every-recording-since-1986","6. ISRC Codes Have Tracked Every Recording Since 1986",[842,46633,46634,46636],{},[996,46635,45346],{}," Every commercially released recording has a unique 12-character identifier called an ISRC (International Standard Recording Code).",[842,46638,46639,46640],{},"Structure: ",[895,46641,46642],{},"CC-XXX-YY-NNNNN",[958,46644,46645,46648,46651,46654],{},[961,46646,46647],{},"CC = Country code",[961,46649,46650],{},"XXX = Registrant code",[961,46652,46653],{},"YY = Year of reference",[961,46655,46656],{},"NNNNN = Designation code",[842,46658,46659],{},"Over 100 million ISRCs have been assigned. When you stream a song, the ISRC is how streaming platforms identify exactly which recording to pay royalties for - even if there are 50 different versions of \"Happy Birthday.\"",[4937,46661],{},[863,46663,46665],{"id":46664},"_7-musescore-has-more-users-than-finale-and-sibelius-combined","7. MuseScore Has More Users Than Finale and Sibelius Combined",[842,46667,46668,46670],{},[996,46669,45346],{}," MuseScore, the free and open-source notation software, has over 10 million users worldwide - more than the commercial giants Finale and Sibelius combined.",[842,46672,46673],{},"The software is entirely community-driven, with contributions from musicians and developers globally. Its companion site, musescore.com, hosts millions of user-created scores that can be viewed, played back, and downloaded.",[842,46675,46676,46679],{},[996,46677,46678],{},"Why it matters:"," Professional music notation software costs $300-600. MuseScore democratizes music education by giving everyone access to publication-quality engraving tools for free.",[4937,46681],{},[863,46683,46685],{"id":46684},"_8-the-loudness-wars-are-officially-over-thanks-to-standards","8. The Loudness Wars Are Officially Over (Thanks to Standards)",[842,46687,46688,46690],{},[996,46689,45346],{}," Streaming platforms now normalize audio loudness, effectively ending the \"loudness wars\" where albums were mastered as loud as possible to stand out on radio.",[842,46692,46693],{},"All major platforms use LUFS (Loudness Units Full Scale) normalization:",[958,46695,46696,46699,46702,46705],{},[961,46697,46698],{},"Spotify: -14 LUFS",[961,46700,46701],{},"Apple Music: -16 LUFS",[961,46703,46704],{},"YouTube: -14 LUFS",[961,46706,46707],{},"Amazon Music: -14 LUFS",[842,46709,46710],{},"If you master a track at -8 LUFS (extremely loud), the platform will turn it down. If you master at -18 LUFS (very dynamic), it gets turned up. The result? Dynamic, well-mastered music no longer loses to \"loud\" masters.",[4937,46712],{},[863,46714,46716],{"id":46715},"_9-ai-can-now-separate-any-song-into-individual-stems","9. AI Can Now Separate Any Song Into Individual Stems",[842,46718,46719,46721],{},[996,46720,45346],{}," Tools like LALAL.AI, Demucs, and Spleeter can separate a mixed song into individual stems (vocals, drums, bass, other) with remarkable quality.",[842,46723,46724],{},"This technology uses deep neural networks trained on thousands of songs where the original stems were available. The AI learned to recognize which frequencies belong to which instruments and can now \"unmix\" songs it has never heard before.",[842,46726,46727],{},[996,46728,46729],{},"Use cases:",[958,46731,46732,46735,46738,46741],{},[961,46733,46734],{},"DJs creating acapellas for remixes",[961,46736,46737],{},"Musicians learning parts from recordings",[961,46739,46740],{},"Remastering old recordings where original tapes are lost",[961,46742,46743],{},"Karaoke without the cheesy MIDI backing tracks",[4937,46745],{},[863,46747,46749],{"id":46748},"_10-web-audio-api-turns-every-browser-into-a-synthesizer","10. Web Audio API Turns Every Browser Into a Synthesizer",[842,46751,46752,46754],{},[996,46753,45346],{}," Modern web browsers include a complete audio synthesis engine accessible through JavaScript. The Web Audio API can:",[958,46756,46757,46760,46763,46766,46769],{},[961,46758,46759],{},"Generate waveforms (sine, square, sawtooth, triangle)",[961,46761,46762],{},"Apply filters, compression, and effects",[961,46764,46765],{},"Analyze audio in real-time (FFT, waveform visualization)",[961,46767,46768],{},"Create spatial 3D audio",[961,46770,46771],{},"Process microphone input",[842,46773,46774],{},"No plugins needed. Tools like Tone.js wrap this API to create full-featured DAWs that run entirely in the browser. You can build instruments, effects, and even complete music production tools using just JavaScript.",[1013,46776,46778],{"className":33433,"code":46777,"language":33435,"meta":728,"style":728},"// Create a simple synthesizer in 5 lines\nconst ctx = new AudioContext();\nconst osc = ctx.createOscillator();\nosc.frequency.value = 440; // A4\nosc.connect(ctx.destination);\nosc.start();\n",[895,46779,46780,46785,46803,46824,46849,46868],{"__ignoreMap":728},[1086,46781,46782],{"class":1088,"line":1089},[1086,46783,46784],{"class":1427},"// Create a simple synthesizer in 5 lines\n",[1086,46786,46787,46789,46792,46794,46796,46799,46801],{"class":1088,"line":729},[1086,46788,33442],{"class":1155},[1086,46790,46791],{"class":1436}," ctx ",[1086,46793,1440],{"class":1146},[1086,46795,26591],{"class":1146},[1086,46797,46798],{"class":1105}," AudioContext",[1086,46800,2516],{"class":1436},[1086,46802,33466],{"class":1146},[1086,46804,46805,46807,46810,46812,46815,46817,46820,46822],{"class":1088,"line":1112},[1086,46806,33442],{"class":1155},[1086,46808,46809],{"class":1436}," osc ",[1086,46811,1440],{"class":1146},[1086,46813,46814],{"class":1436}," ctx",[1086,46816,861],{"class":1146},[1086,46818,46819],{"class":1105},"createOscillator",[1086,46821,2516],{"class":1436},[1086,46823,33466],{"class":1146},[1086,46825,46826,46829,46831,46834,46836,46839,46841,46844,46846],{"class":1088,"line":1181},[1086,46827,46828],{"class":1436},"osc",[1086,46830,861],{"class":1146},[1086,46832,46833],{"class":1436},"frequency",[1086,46835,861],{"class":1146},[1086,46837,46838],{"class":1436},"value ",[1086,46840,1440],{"class":1146},[1086,46842,46843],{"class":1187}," 440",[1086,46845,4639],{"class":1146},[1086,46847,46848],{"class":1427}," // A4\n",[1086,46850,46851,46853,46855,46858,46861,46863,46866],{"class":1088,"line":1205},[1086,46852,46828],{"class":1436},[1086,46854,861],{"class":1146},[1086,46856,46857],{"class":1105},"connect",[1086,46859,46860],{"class":1436},"(ctx",[1086,46862,861],{"class":1146},[1086,46864,46865],{"class":1436},"destination)",[1086,46867,33466],{"class":1146},[1086,46869,46870,46872,46874,46876,46878],{"class":1088,"line":1276},[1086,46871,46828],{"class":1436},[1086,46873,861],{"class":1146},[1086,46875,29437],{"class":1105},[1086,46877,2516],{"class":1436},[1086,46879,33466],{"class":1146},[4937,46881],{},[863,46883,31693],{"id":16446},[842,46885,46886],{},"This is Part 2 of an ongoing series exploring the fascinating world of music technology. Each installment will bring new facts, innovations, and insights from the intersection of music and technology.",[842,46888,46889,46890,46440],{},"Know a surprising MusicTech fact? ",[846,46891,46439],{"href":4946,"rel":46892},[850],[4937,46894],{},[842,46896,46897],{},[964,46898,46899,46900,46904],{},"MusicTech Lab builds software for the music industry. From DDEX integrations to audio analysis tools, we've seen the technology behind the music. ",[846,46901,46903],{"href":4946,"rel":46902},[850],"Contact us"," if you're building something in the MusicTech space.",[1680,46906,46907],{},"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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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);}",{"title":728,"searchDepth":729,"depth":729,"links":46909},[46910,46911,46912,46913,46914,46915,46916,46917,46918,46919,46920],{"id":46489,"depth":729,"text":46490},{"id":46512,"depth":729,"text":46513},{"id":46532,"depth":729,"text":46533},{"id":46572,"depth":729,"text":46573},{"id":46613,"depth":729,"text":46614},{"id":46630,"depth":729,"text":46631},{"id":46664,"depth":729,"text":46665},{"id":46684,"depth":729,"text":46685},{"id":46715,"depth":729,"text":46716},{"id":46748,"depth":729,"text":46749},{"id":16446,"depth":729,"text":31693},"2026-01-10T00:00:00.000Z","Fascinating facts about music technology, standards, and innovations that shape how we create, share, and experience music. Part 2 of the series.",{"src":46924},"/images/blog/musictechlab_blog_did-you-know-musictech-facts.webp",{"enabled":738,"items":46926},[46927,46929,46931,46933],{"text":46928,"icon":9547},"MIDI is over 40 years old (1983) and still the industry standard for music production.",{"text":46930,"icon":40852},"MusicXML enables blind musicians to convert sheet music to braille notation.",{"text":46932,"icon":3844},"Spotify calculates danceability, valence, energy, and speechiness for every track.",{"text":46934,"icon":2895},"DDEX standards power metadata exchange across all major music platforms.",{},{"title":442,"description":46922},[5523],"8NgykFhlDgcuwNiDO1qcmgBSuqXtOE3LKMjWSzYgNKk",{"id":46940,"title":26,"authors":46941,"badge":723,"body":46945,"category":4990,"client":50069,"date":50071,"description":50072,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":50073,"keyTakeaways":50075,"meta":50085,"navigation":738,"path":27,"seo":50086,"status":723,"stem":28,"tags":50087,"teaser":723,"__hash__":50088},"posts/blog/case-study/how-we-built-memosonic-accessible-audio-memory-game-flutter.md",[46942],{"name":834,"to":46943,"avatar":46944},"https://www.linkedin.com/in/msmenzyk",{"src":722},{"type":725,"value":46946,"toc":50008},[46947,46951,46954,46960,46963,46981,46983,46987,46990,47010,47013,47017,47063,47065,47069,47073,47076,47087,47093,47097,47103,47106,47130,47133,47139,47141,47145,47148,47155,47160,47166,47170,47173,47193,47427,47437,47440,47446,47450,47456,47675,47681,47687,47693,47696,47991,47994,47998,48004,48236,48239,48243,48246,48252,48357,48363,48385,48391,48456,48460,48463,48542,48548,48551,48554,48580,48586,48588,48592,48595,48599,48615,48620,48625,48630,48635,48639,48644,48649,48651,48655,48658,48662,48665,48671,48685,48689,48692,48698,48700,48704,48707,48764,48778,48781,48784,48786,48790,48793,48797,48800,48805,48808,48822,48836,48840,48843,48848,48862,48867,48878,48883,48894,48899,48910,48912,48916,48920,48926,48930,49020,49024,49027,49244,49251,49253,49256,49260,49263,49267,49270,49281,49285,49288,49292,49295,49308,49311,49313,49317,49323,49326,49331,49342,49347,49358,49363,49374,49379,49382,49387,49395,49406,49409,49411,49415,49418,49423,49437,49441,49444,49458,49461,49467,49470,49474,49480,49490,49496,49502,49506,49509,49547,49550,49554,49557,49560,49563,49589,49593,49596,49601,49604,49610,49616,49621,49623,49625,49628,49660,49663,49665,49669,49672,49698,49701,49703,49705,49735,49737,49741,49744,49748,49757,49760,49816,49820,49826,49840,49846,49852,49856,49859,49862,49900,49903,49917,49921,49928,49931,49934,49937,49963,49970,49973,49975,49979,50005],[863,46948,46950],{"id":46949},"it-started-with-a-simple-game","It Started with a Simple Game",[842,46952,46953],{},"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.",[842,46955,46956,46957],{},"The kids loved it. But something clicked in my head: ",[964,46958,46959],{},"What if instead of matching pictures, we matched sounds?",[842,46961,46962],{},"That question sparked MemoSonic.",[1045,46964,31345,46968,31345,46972,31345,46978],{"className":46965},[13033,46966,46967,13034],"flex-row","items-center",[1045,46969],{"className":46970},[46971],"w-1/4",[1027,46973],{"src":46974,"alt":46975,"className":46976},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_0.webp","MemoSonic home screen",[46977],"w-1/2",[1045,46979],{"className":46980},[46971],[4937,46982],{},[863,46984,46986],{"id":46985},"the-problem-visual-learning-isnt-everything","The Problem: Visual Learning Isn't Everything",[842,46988,46989],{},"Traditional memory games are purely visual. You see an image, remember its position, find its pair. Great for training visual memory, but what about:",[958,46991,46992,46998,47004],{},[961,46993,46994,46997],{},[996,46995,46996],{},"Auditory learners"," who process information better through sound?",[961,46999,47000,47003],{},[996,47001,47002],{},"Young musicians"," trying to recognize chords, scales, or instrument timbres?",[961,47005,47006,47009],{},[996,47007,47008],{},"Visually impaired children"," who can't participate in traditional memory games at all?",[842,47011,47012],{},"We realized there was a gap. A big one.",[1074,47014,47016],{"id":47015},"what-we-wanted-to-build","What We Wanted to Build:",[871,47018,47019,47029],{},[874,47020,47021],{},[877,47022,47023,47026],{},[880,47024,47025],{},"Traditional Memory",[880,47027,47028],{},"MemoSonic",[887,47030,47031,47039,47047,47055],{},[877,47032,47033,47036],{},[892,47034,47035],{},"Visual only",[892,47037,47038],{},"Sound-first approach",[877,47040,47041,47044],{},[892,47042,47043],{},"Static images",[892,47045,47046],{},"Interactive audio feedback",[877,47048,47049,47052],{},[892,47050,47051],{},"Limited accessibility",[892,47053,47054],{},"Inclusive by design",[877,47056,47057,47060],{},[892,47058,47059],{},"One learning style",[892,47061,47062],{},"Multiple categories for different interests",[4937,47064],{},[863,47066,47068],{"id":47067},"building-memosonic-from-idea-to-app","Building MemoSonic: From Idea to App",[1074,47070,47072],{"id":47071},"choosing-the-tech-stack","Choosing the Tech Stack",[842,47074,47075],{},"We needed something that would:",[958,47077,47078,47081,47084],{},[961,47079,47080],{},"Work on both iOS and Android",[961,47082,47083],{},"Handle audio playback smoothly",[961,47085,47086],{},"Feel responsive and polished",[842,47088,47089,47092],{},[996,47090,47091],{},"Flutter"," was the obvious choice. Cross-platform, beautiful animations out of the box, and a fantastic ecosystem for audio libraries.",[1074,47094,47096],{"id":47095},"the-audio-challenge","The Audio Challenge",[842,47098,47099,47100,861],{},"Here's something most developers don't think about: ",[996,47101,47102],{},"audio is hard",[842,47104,47105],{},"Not the playback itself - that's straightforward. The hard part is:",[991,47107,47108,47118,47124],{},[961,47109,47110,47113,47114,47117],{},[996,47111,47112],{},"Timing"," - When a player taps a card, the sound needs to play ",[964,47115,47116],{},"instantly",". Even 100ms delay feels wrong.",[961,47119,47120,47123],{},[996,47121,47122],{},"Overlapping sounds"," - What happens when someone taps two cards quickly? Do sounds cut off? Layer?",[961,47125,47126,47129],{},[996,47127,47128],{},"Memory management"," - 100+ audio files across categories. Load them all? Stream them? Preload the current level?",[842,47131,47132],{},"We went through several iterations:",[1013,47134,47137],{"className":47135,"code":47136,"language":1018},[1016],"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",[895,47138,47136],{"__ignoreMap":728},[4937,47140],{},[863,47142,47144],{"id":47143},"the-content-pipeline-generating-300-audio-assets","The Content Pipeline: Generating 300+ Audio Assets",[842,47146,47147],{},"Here's where it gets nerdy (in the best way).",[842,47149,47150,47151,47154],{},"When we started MemoSonic, we had a problem: we needed ",[996,47152,47153],{},"hundreds of audio files",". 168 chord sounds. 14 scale recordings. 8 rhythm patterns. Individual notes. Where do you even get that?",[842,47156,47157],{},[996,47158,47159],{},"Answer: You generate them programmatically.",[842,47161,47162],{},[1027,47163],{"alt":47164,"src":47165},"Terminal output","/images/blog/musictechlab_blog_how-we-built-memosonic_inline_2.png",[1074,47167,47169],{"id":47168},"synthesizing-piano-chords-with-fluidsynth","Synthesizing Piano Chords with FluidSynth",[842,47171,47172],{},"Instead of recording a pianist playing every chord (expensive, time-consuming), we wrote Python scripts that:",[991,47174,47175,47181,47187],{},[961,47176,47177,47180],{},[996,47178,47179],{},"Generate MIDI files"," with the exact notes for each chord",[961,47182,47183,47186],{},[996,47184,47185],{},"Render them through FluidSynth"," using a high-quality Salamander Grand Piano soundfont",[961,47188,47189,47192],{},[996,47190,47191],{},"Convert to MP3"," via FFmpeg",[1013,47194,47196],{"className":1368,"code":47195,"language":1250,"meta":728,"style":728},"# 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",[895,47197,47198,47203,47212,47241,47268,47300,47332,47365,47394,47423],{"__ignoreMap":728},[1086,47199,47200],{"class":1088,"line":1089},[1086,47201,47202],{"class":1427},"# Chord intervals (semitones from root)\n",[1086,47204,47205,47208,47210],{"class":1088,"line":729},[1086,47206,47207],{"class":1436},"CHORD_TYPES ",[1086,47209,1440],{"class":1146},[1086,47211,1164],{"class":1146},[1086,47213,47214,47216,47219,47221,47223,47225,47227,47229,47231,47233,47235,47238],{"class":1088,"line":1112},[1086,47215,1169],{"class":1146},[1086,47217,47218],{"class":1096},"maj",[1086,47220,1159],{"class":1146},[1086,47222,1133],{"class":1146},[1086,47224,1217],{"class":1146},[1086,47226,4417],{"class":1187},[1086,47228,1227],{"class":1146},[1086,47230,30711],{"class":1187},[1086,47232,1227],{"class":1146},[1086,47234,42504],{"class":1187},[1086,47236,47237],{"class":1146},"],",[1086,47239,47240],{"class":1427},"           # Major: root, major 3rd, perfect 5th\n",[1086,47242,47243,47245,47247,47249,47251,47253,47255,47257,47259,47261,47263,47265],{"class":1088,"line":1181},[1086,47244,1169],{"class":1146},[1086,47246,41803],{"class":1096},[1086,47248,1159],{"class":1146},[1086,47250,1133],{"class":1146},[1086,47252,1217],{"class":1146},[1086,47254,4417],{"class":1187},[1086,47256,1227],{"class":1146},[1086,47258,9214],{"class":1187},[1086,47260,1227],{"class":1146},[1086,47262,42504],{"class":1187},[1086,47264,47237],{"class":1146},[1086,47266,47267],{"class":1427},"           # Minor: root, minor 3rd, perfect 5th\n",[1086,47269,47270,47272,47274,47276,47278,47280,47282,47284,47286,47288,47290,47292,47295,47297],{"class":1088,"line":1205},[1086,47271,1169],{"class":1146},[1086,47273,25810],{"class":1096},[1086,47275,1159],{"class":1146},[1086,47277,1133],{"class":1146},[1086,47279,1217],{"class":1146},[1086,47281,4417],{"class":1187},[1086,47283,1227],{"class":1146},[1086,47285,30711],{"class":1187},[1086,47287,1227],{"class":1146},[1086,47289,42504],{"class":1187},[1086,47291,1227],{"class":1146},[1086,47293,47294],{"class":1187}," 10",[1086,47296,47237],{"class":1146},[1086,47298,47299],{"class":1427},"         # Dominant 7th\n",[1086,47301,47302,47304,47307,47309,47311,47313,47315,47317,47319,47321,47323,47325,47327,47329],{"class":1088,"line":1276},[1086,47303,1169],{"class":1146},[1086,47305,47306],{"class":1096},"m7",[1086,47308,1159],{"class":1146},[1086,47310,1133],{"class":1146},[1086,47312,1217],{"class":1146},[1086,47314,4417],{"class":1187},[1086,47316,1227],{"class":1146},[1086,47318,9214],{"class":1187},[1086,47320,1227],{"class":1146},[1086,47322,42504],{"class":1187},[1086,47324,1227],{"class":1146},[1086,47326,47294],{"class":1187},[1086,47328,47237],{"class":1146},[1086,47330,47331],{"class":1427},"        # Minor 7th\n",[1086,47333,47334,47336,47339,47341,47343,47345,47347,47349,47351,47353,47355,47357,47360,47362],{"class":1088,"line":1282},[1086,47335,1169],{"class":1146},[1086,47337,47338],{"class":1096},"maj7",[1086,47340,1159],{"class":1146},[1086,47342,1133],{"class":1146},[1086,47344,1217],{"class":1146},[1086,47346,4417],{"class":1187},[1086,47348,1227],{"class":1146},[1086,47350,30711],{"class":1187},[1086,47352,1227],{"class":1146},[1086,47354,42504],{"class":1187},[1086,47356,1227],{"class":1146},[1086,47358,47359],{"class":1187}," 11",[1086,47361,47237],{"class":1146},[1086,47363,47364],{"class":1427},"      # Major 7th\n",[1086,47366,47367,47369,47372,47374,47376,47378,47380,47382,47384,47386,47389,47391],{"class":1088,"line":1288},[1086,47368,1169],{"class":1146},[1086,47370,47371],{"class":1096},"aug",[1086,47373,1159],{"class":1146},[1086,47375,1133],{"class":1146},[1086,47377,1217],{"class":1146},[1086,47379,4417],{"class":1187},[1086,47381,1227],{"class":1146},[1086,47383,30711],{"class":1187},[1086,47385,1227],{"class":1146},[1086,47387,47388],{"class":1187}," 8",[1086,47390,47237],{"class":1146},[1086,47392,47393],{"class":1427},"           # Augmented\n",[1086,47395,47396,47398,47401,47403,47405,47407,47409,47411,47413,47415,47418,47420],{"class":1088,"line":2685},[1086,47397,1169],{"class":1146},[1086,47399,47400],{"class":1096},"dim",[1086,47402,1159],{"class":1146},[1086,47404,1133],{"class":1146},[1086,47406,1217],{"class":1146},[1086,47408,4417],{"class":1187},[1086,47410,1227],{"class":1146},[1086,47412,9214],{"class":1187},[1086,47414,1227],{"class":1146},[1086,47416,47417],{"class":1187}," 6",[1086,47419,47237],{"class":1146},[1086,47421,47422],{"class":1427},"           # Diminished\n",[1086,47424,47425],{"class":1088,"line":2700},[1086,47426,1291],{"class":1146},[842,47428,47429,47430,47433,47434,861],{},"12 root notes × 7 chord types = ",[996,47431,47432],{},"84 chords",". Add enharmonic equivalents (C# = Db, etc.) and we hit ",[996,47435,47436],{},"168 unique chord files",[842,47438,47439],{},"The pipeline:",[1013,47441,47444],{"className":47442,"code":47443,"language":1018},[1016],"MIDI generation (midiutil)\n    ↓\nFluidSynth + Salamander Piano SF2\n    ↓\nWAV file\n    ↓\nFFmpeg MP3 encoding\n    ↓\nFinal audio asset\n",[895,47445,47443],{"__ignoreMap":728},[1074,47447,47449],{"id":47448},"synthesizing-drum-patterns-from-scratch","Synthesizing Drum Patterns from Scratch",[842,47451,47452,47453,861],{},"The rhythm category was even more interesting. We didn't use samples at all - we ",[996,47454,47455],{},"synthesized every drum sound mathematically",[1013,47457,47459],{"className":1368,"code":47458,"language":1250,"meta":728,"style":728},"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",[895,47460,47461,47480,47489,47507,47521,47526,47574,47578,47583,47621,47625,47630,47644,47670],{"__ignoreMap":728},[1086,47462,47463,47465,47468,47470,47473,47475,47478],{"class":1088,"line":1089},[1086,47464,1392],{"class":1155},[1086,47466,47467],{"class":1105}," generate_kick",[1086,47469,1398],{"class":1146},[1086,47471,47472],{"class":1401},"duration_ms",[1086,47474,1440],{"class":1146},[1086,47476,47477],{"class":1187},"150",[1086,47479,4047],{"class":1146},[1086,47481,47482,47484,47487],{"class":1088,"line":729},[1086,47483,1424],{"class":1423},[1086,47485,47486],{"class":1427},"Generate a punchy kick drum sound",[1086,47488,1431],{"class":1423},[1086,47490,47491,47493,47495,47497,47500,47502,47505],{"class":1088,"line":1112},[1086,47492,5925],{"class":1423},[1086,47494,42491],{"class":1436},[1086,47496,5931],{"class":1423},[1086,47498,47499],{"class":1105}," range",[1086,47501,1398],{"class":1146},[1086,47503,47504],{"class":1105},"num_samples",[1086,47506,4047],{"class":1146},[1086,47508,47509,47512,47514,47516,47518],{"class":1088,"line":1181},[1086,47510,47511],{"class":1436},"        t ",[1086,47513,1440],{"class":1146},[1086,47515,42491],{"class":1436},[1086,47517,23036],{"class":1146},[1086,47519,47520],{"class":1436}," SAMPLE_RATE\n",[1086,47522,47523],{"class":1088,"line":1205},[1086,47524,47525],{"class":1427},"        # Pitch envelope: starts high, drops quickly\n",[1086,47527,47528,47531,47533,47536,47538,47540,47543,47545,47548,47550,47553,47556,47558,47561,47564,47567,47569,47572],{"class":1088,"line":1276},[1086,47529,47530],{"class":1436},"        freq ",[1086,47532,1440],{"class":1146},[1086,47534,47535],{"class":1436}," freq_end ",[1086,47537,30708],{"class":1146},[1086,47539,5979],{"class":1146},[1086,47541,47542],{"class":1436},"freq_start ",[1086,47544,2635],{"class":1146},[1086,47546,47547],{"class":1436}," freq_end",[1086,47549,1410],{"class":1146},[1086,47551,47552],{"class":1146}," *",[1086,47554,47555],{"class":1436}," math",[1086,47557,861],{"class":1146},[1086,47559,47560],{"class":1105},"exp",[1086,47562,47563],{"class":1146},"(-",[1086,47565,47566],{"class":1105},"decay_rate ",[1086,47568,2775],{"class":1146},[1086,47570,47571],{"class":1105}," t",[1086,47573,1455],{"class":1146},[1086,47575,47576],{"class":1088,"line":1282},[1086,47577,3390],{"emptyLinePlaceholder":738},[1086,47579,47580],{"class":1088,"line":1288},[1086,47581,47582],{"class":1427},"        # Main tone with pitch drop\n",[1086,47584,47585,47588,47590,47592,47594,47597,47599,47601,47603,47605,47607,47610,47612,47615,47617,47619],{"class":1088,"line":2685},[1086,47586,47587],{"class":1436},"        tone ",[1086,47589,1440],{"class":1146},[1086,47591,47555],{"class":1436},[1086,47593,861],{"class":1146},[1086,47595,47596],{"class":1105},"sin",[1086,47598,1398],{"class":1146},[1086,47600,1483],{"class":1187},[1086,47602,47552],{"class":1146},[1086,47604,47555],{"class":1105},[1086,47606,861],{"class":1146},[1086,47608,47609],{"class":4109},"pi",[1086,47611,47552],{"class":1146},[1086,47613,47614],{"class":1105}," freq ",[1086,47616,2775],{"class":1146},[1086,47618,47571],{"class":1105},[1086,47620,1455],{"class":1146},[1086,47622,47623],{"class":1088,"line":2700},[1086,47624,3390],{"emptyLinePlaceholder":738},[1086,47626,47627],{"class":1088,"line":3398},[1086,47628,47629],{"class":1427},"        # Add click transient at the start\n",[1086,47631,47632,47634,47637,47639,47642],{"class":1088,"line":1715},[1086,47633,6800],{"class":1423},[1086,47635,47636],{"class":1436}," t ",[1086,47638,11164],{"class":1146},[1086,47640,47641],{"class":1187}," 0.005",[1086,47643,1418],{"class":1146},[1086,47645,47646,47649,47651,47653,47655,47657,47659,47661,47663,47665,47667],{"class":1088,"line":3409},[1086,47647,47648],{"class":1436},"            click ",[1086,47650,1440],{"class":1146},[1086,47652,5979],{"class":1146},[1086,47654,4434],{"class":1187},[1086,47656,2816],{"class":1146},[1086,47658,47636],{"class":1436},[1086,47660,23036],{"class":1146},[1086,47662,47641],{"class":1187},[1086,47664,1410],{"class":1146},[1086,47666,47552],{"class":1146},[1086,47668,47669],{"class":1187}," 0.5\n",[1086,47671,47672],{"class":1088,"line":3415},[1086,47673,47674],{"class":1436},"        ...\n",[842,47676,47677,47680],{},[996,47678,47679],{},"Kick drum"," - A sine wave that rapidly drops in pitch (150Hz → 50Hz) with a click transient.",[842,47682,47683,47686],{},[996,47684,47685],{},"Snare drum"," - Shell resonance (170Hz with harmonics) + attack transient (450Hz) + filtered noise for the snare wires. Plus a touch of room reverb.",[842,47688,47689,47692],{},[996,47690,47691],{},"Hi-hat"," - White noise mixed with metallic high-frequency tones (6kHz, 8kHz, 10kHz), shaped with ADSR envelopes.",[842,47694,47695],{},"Then we programmed the actual rhythm patterns:",[1013,47697,47699],{"className":1368,"code":47698,"language":1250,"meta":728,"style":728},"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",[895,47700,47701,47710,47749,47774,47800,47825,47850,47855,47879,47902,47906,47911,47948,47953,47979,47983,47987],{"__ignoreMap":728},[1086,47702,47703,47706,47708],{"class":1088,"line":1089},[1086,47704,47705],{"class":1436},"RHYTHMS ",[1086,47707,1440],{"class":1146},[1086,47709,6580],{"class":1146},[1086,47711,47712,47715,47717,47720,47722,47724,47726,47729,47731,47733,47735,47738,47740,47742,47745,47747],{"class":1088,"line":729},[1086,47713,47714],{"class":1146},"    (",[1086,47716,1159],{"class":1146},[1086,47718,47719],{"class":1096},"4_4",[1086,47721,1159],{"class":1146},[1086,47723,1227],{"class":1146},[1086,47725,1195],{"class":1146},[1086,47727,47728],{"class":1096},"4/4 Time",[1086,47730,1159],{"class":1146},[1086,47732,1227],{"class":1146},[1086,47734,1195],{"class":1146},[1086,47736,47737],{"class":1096},"4/4",[1086,47739,1159],{"class":1146},[1086,47741,1227],{"class":1146},[1086,47743,47744],{"class":1187}," 100",[1086,47746,1227],{"class":1146},[1086,47748,6580],{"class":1146},[1086,47750,47751,47754,47756,47758,47760,47763,47765,47767,47769,47771],{"class":1088,"line":1112},[1086,47752,47753],{"class":1146},"        (",[1086,47755,4417],{"class":1187},[1086,47757,1227],{"class":1146},[1086,47759,1195],{"class":1146},[1086,47761,47762],{"class":1096},"kick",[1086,47764,1159],{"class":1146},[1086,47766,1227],{"class":1146},[1086,47768,22718],{"class":1187},[1086,47770,4179],{"class":1146},[1086,47772,47773],{"class":1427},"      # Beat 1\n",[1086,47775,47776,47778,47780,47782,47784,47787,47789,47791,47793,47795,47797],{"class":1088,"line":1181},[1086,47777,47753],{"class":1146},[1086,47779,4434],{"class":1187},[1086,47781,1227],{"class":1146},[1086,47783,1195],{"class":1146},[1086,47785,47786],{"class":1096},"snare",[1086,47788,1159],{"class":1146},[1086,47790,1227],{"class":1146},[1086,47792,2816],{"class":1146},[1086,47794,1483],{"class":1187},[1086,47796,4179],{"class":1146},[1086,47798,47799],{"class":1427},"    # Beat 2\n",[1086,47801,47802,47804,47806,47808,47810,47812,47814,47816,47818,47820,47822],{"class":1088,"line":1205},[1086,47803,47753],{"class":1146},[1086,47805,1483],{"class":1187},[1086,47807,1227],{"class":1146},[1086,47809,1195],{"class":1146},[1086,47811,47762],{"class":1096},[1086,47813,1159],{"class":1146},[1086,47815,1227],{"class":1146},[1086,47817,2816],{"class":1146},[1086,47819,1483],{"class":1187},[1086,47821,4179],{"class":1146},[1086,47823,47824],{"class":1427},"     # Beat 3\n",[1086,47826,47827,47829,47831,47833,47835,47837,47839,47841,47843,47845,47847],{"class":1088,"line":1276},[1086,47828,47753],{"class":1146},[1086,47830,7948],{"class":1187},[1086,47832,1227],{"class":1146},[1086,47834,1195],{"class":1146},[1086,47836,47786],{"class":1096},[1086,47838,1159],{"class":1146},[1086,47840,1227],{"class":1146},[1086,47842,2816],{"class":1146},[1086,47844,1483],{"class":1187},[1086,47846,4179],{"class":1146},[1086,47848,47849],{"class":1427},"    # Beat 4\n",[1086,47851,47852],{"class":1088,"line":1282},[1086,47853,47854],{"class":1427},"        # Off-beat hi-hats\n",[1086,47856,47857,47859,47862,47864,47866,47869,47871,47873,47875,47877],{"class":1088,"line":1288},[1086,47858,47753],{"class":1146},[1086,47860,47861],{"class":1187},"0.5",[1086,47863,1227],{"class":1146},[1086,47865,1195],{"class":1146},[1086,47867,47868],{"class":1096},"hihat",[1086,47870,1159],{"class":1146},[1086,47872,1227],{"class":1146},[1086,47874,2816],{"class":1146},[1086,47876,7970],{"class":1187},[1086,47878,20449],{"class":1146},[1086,47880,47881,47883,47886,47888,47890,47892,47894,47896,47898,47900],{"class":1088,"line":2685},[1086,47882,47753],{"class":1146},[1086,47884,47885],{"class":1187},"1.5",[1086,47887,1227],{"class":1146},[1086,47889,1195],{"class":1146},[1086,47891,47868],{"class":1096},[1086,47893,1159],{"class":1146},[1086,47895,1227],{"class":1146},[1086,47897,2816],{"class":1146},[1086,47899,7970],{"class":1187},[1086,47901,20449],{"class":1146},[1086,47903,47904],{"class":1088,"line":2700},[1086,47905,47674],{"class":1436},[1086,47907,47908],{"class":1088,"line":3398},[1086,47909,47910],{"class":1146},"    ]),\n",[1086,47912,47913,47915,47917,47920,47922,47924,47926,47929,47931,47933,47935,47937,47939,47941,47944,47946],{"class":1088,"line":1715},[1086,47914,47714],{"class":1146},[1086,47916,1159],{"class":1146},[1086,47918,47919],{"class":1096},"swing",[1086,47921,1159],{"class":1146},[1086,47923,1227],{"class":1146},[1086,47925,1195],{"class":1146},[1086,47927,47928],{"class":1096},"Swing",[1086,47930,1159],{"class":1146},[1086,47932,1227],{"class":1146},[1086,47934,1195],{"class":1146},[1086,47936,47737],{"class":1096},[1086,47938,1159],{"class":1146},[1086,47940,1227],{"class":1146},[1086,47942,47943],{"class":1187}," 120",[1086,47945,1227],{"class":1146},[1086,47947,6580],{"class":1146},[1086,47949,47950],{"class":1088,"line":3409},[1086,47951,47952],{"class":1427},"        # Triplet-based timing for swing feel\n",[1086,47954,47955,47957,47960,47962,47964,47966,47968,47970,47972,47974,47976],{"class":1088,"line":3415},[1086,47956,47753],{"class":1146},[1086,47958,47959],{"class":1187},"0.66",[1086,47961,1227],{"class":1146},[1086,47963,1195],{"class":1146},[1086,47965,47868],{"class":1096},[1086,47967,1159],{"class":1146},[1086,47969,1227],{"class":1146},[1086,47971,2816],{"class":1146},[1086,47973,7970],{"class":1187},[1086,47975,4179],{"class":1146},[1086,47977,47978],{"class":1427},"  # Swung eighth\n",[1086,47980,47981],{"class":1088,"line":3421},[1086,47982,47674],{"class":1436},[1086,47984,47985],{"class":1088,"line":3427},[1086,47986,47910],{"class":1146},[1086,47988,47989],{"class":1088,"line":3433},[1086,47990,1273],{"class":1146},[842,47992,47993],{},"8 rhythm patterns, each with its own BPM, time signature, and accent patterns.",[1074,47995,47997],{"id":47996},"pure-tones-for-note-training","Pure Tones for Note Training",[842,47999,48000,48001,1133],{},"For the Notes category, we used ",[996,48002,48003],{},"pydub's sine wave generator",[1013,48005,48007],{"className":1368,"code":48006,"language":1250,"meta":728,"style":728},"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",[895,48008,48009,48018,48047,48077,48106,48111,48141,48145,48149,48167,48196,48229],{"__ignoreMap":728},[1086,48010,48011,48014,48016],{"class":1088,"line":1089},[1086,48012,48013],{"class":1436},"NOTES ",[1086,48015,1440],{"class":1146},[1086,48017,6580],{"class":1146},[1086,48019,48020,48022,48024,48027,48029,48031,48033,48035,48037,48039,48042,48044],{"class":1088,"line":729},[1086,48021,47714],{"class":1146},[1086,48023,1159],{"class":1146},[1086,48025,48026],{"class":1096},"c",[1086,48028,1159],{"class":1146},[1086,48030,1227],{"class":1146},[1086,48032,1195],{"class":1146},[1086,48034,44616],{"class":1096},[1086,48036,1159],{"class":1146},[1086,48038,1227],{"class":1146},[1086,48040,48041],{"class":1187}," 261.63",[1086,48043,4179],{"class":1146},[1086,48045,48046],{"class":1427},"  # C4 (Middle C)\n",[1086,48048,48049,48051,48053,48056,48058,48060,48062,48065,48067,48069,48072,48074],{"class":1088,"line":1112},[1086,48050,47714],{"class":1146},[1086,48052,1159],{"class":1146},[1086,48054,48055],{"class":1096},"d",[1086,48057,1159],{"class":1146},[1086,48059,1227],{"class":1146},[1086,48061,1195],{"class":1146},[1086,48063,48064],{"class":1096},"D",[1086,48066,1159],{"class":1146},[1086,48068,1227],{"class":1146},[1086,48070,48071],{"class":1187}," 293.66",[1086,48073,4179],{"class":1146},[1086,48075,48076],{"class":1427},"  # D4\n",[1086,48078,48079,48081,48083,48085,48087,48089,48091,48094,48096,48098,48101,48103],{"class":1088,"line":1181},[1086,48080,47714],{"class":1146},[1086,48082,1159],{"class":1146},[1086,48084,39081],{"class":1096},[1086,48086,1159],{"class":1146},[1086,48088,1227],{"class":1146},[1086,48090,1195],{"class":1146},[1086,48092,48093],{"class":1096},"E",[1086,48095,1159],{"class":1146},[1086,48097,1227],{"class":1146},[1086,48099,48100],{"class":1187}," 329.63",[1086,48102,4179],{"class":1146},[1086,48104,48105],{"class":1427},"  # E4\n",[1086,48107,48108],{"class":1088,"line":1205},[1086,48109,48110],{"class":1436},"    ...\n",[1086,48112,48113,48115,48117,48120,48122,48124,48126,48129,48131,48133,48136,48138],{"class":1088,"line":1276},[1086,48114,47714],{"class":1146},[1086,48116,1159],{"class":1146},[1086,48118,48119],{"class":1096},"c_octave",[1086,48121,1159],{"class":1146},[1086,48123,1227],{"class":1146},[1086,48125,1195],{"class":1146},[1086,48127,48128],{"class":1096},"C (Octave)",[1086,48130,1159],{"class":1146},[1086,48132,1227],{"class":1146},[1086,48134,48135],{"class":1187}," 523.25",[1086,48137,4179],{"class":1146},[1086,48139,48140],{"class":1427},"  # C5\n",[1086,48142,48143],{"class":1088,"line":1282},[1086,48144,1273],{"class":1146},[1086,48146,48147],{"class":1088,"line":1288},[1086,48148,3390],{"emptyLinePlaceholder":738},[1086,48150,48151,48153,48156,48158,48160,48162,48165],{"class":1088,"line":2685},[1086,48152,1392],{"class":1155},[1086,48154,48155],{"class":1105}," generate_note_audio",[1086,48157,1398],{"class":1146},[1086,48159,46833],{"class":1401},[1086,48161,1227],{"class":1146},[1086,48163,48164],{"class":1401}," duration_ms",[1086,48166,4047],{"class":1146},[1086,48168,48169,48172,48174,48177,48179,48181,48183,48186,48188,48190,48192,48194],{"class":1088,"line":2700},[1086,48170,48171],{"class":1436},"    tone ",[1086,48173,1440],{"class":1146},[1086,48175,48176],{"class":1105}," Sine",[1086,48178,1398],{"class":1146},[1086,48180,46833],{"class":1105},[1086,48182,6786],{"class":1146},[1086,48184,48185],{"class":1105},"to_audio_segment",[1086,48187,1398],{"class":1146},[1086,48189,30984],{"class":1401},[1086,48191,1440],{"class":1146},[1086,48193,47472],{"class":1105},[1086,48195,1455],{"class":1146},[1086,48197,48198,48200,48202,48205,48207,48210,48212,48215,48217,48220,48222,48224,48226],{"class":1088,"line":3398},[1086,48199,48171],{"class":1436},[1086,48201,1440],{"class":1146},[1086,48203,48204],{"class":1436}," tone",[1086,48206,861],{"class":1146},[1086,48208,48209],{"class":1105},"fade_in",[1086,48211,1398],{"class":1146},[1086,48213,48214],{"class":1187},"50",[1086,48216,6786],{"class":1146},[1086,48218,48219],{"class":1105},"fade_out",[1086,48221,1398],{"class":1146},[1086,48223,24273],{"class":1187},[1086,48225,1410],{"class":1146},[1086,48227,48228],{"class":1427},"  # Avoid clicks\n",[1086,48230,48231,48233],{"class":1088,"line":1715},[1086,48232,1460],{"class":1423},[1086,48234,48235],{"class":1436}," tone\n",[842,48237,48238],{},"Simple, clean, and perfect for ear training.",[1074,48240,48242],{"id":48241},"post-processing-cutting-trimming-normalizing","Post-Processing: Cutting, Trimming, Normalizing",[842,48244,48245],{},"Raw generated audio isn't always game-ready. We wrote additional scripts:",[842,48247,48248,48251],{},[996,48249,48250],{},"Cut scales to ascending only"," - Original scales went up AND down. Too long. We cut them to just the ascending portion:",[1013,48253,48255],{"className":1368,"code":48254,"language":1250,"meta":728,"style":728},"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",[895,48256,48257,48270,48291,48312,48329],{"__ignoreMap":728},[1086,48258,48259,48261,48264,48266,48268],{"class":1088,"line":1089},[1086,48260,1392],{"class":1155},[1086,48262,48263],{"class":1105}," cut_to_ascending",[1086,48265,1398],{"class":1146},[1086,48267,35660],{"class":1401},[1086,48269,4047],{"class":1146},[1086,48271,48272,48275,48277,48280,48282,48285,48287,48289],{"class":1088,"line":729},[1086,48273,48274],{"class":1436},"    audio ",[1086,48276,1440],{"class":1146},[1086,48278,48279],{"class":1436}," AudioSegment",[1086,48281,861],{"class":1146},[1086,48283,48284],{"class":1105},"from_mp3",[1086,48286,1398],{"class":1146},[1086,48288,35660],{"class":1105},[1086,48290,1455],{"class":1146},[1086,48292,48293,48296,48298,48300,48302,48304,48306,48309],{"class":1088,"line":1112},[1086,48294,48295],{"class":1436},"    half_point ",[1086,48297,1440],{"class":1146},[1086,48299,30717],{"class":1105},[1086,48301,1398],{"class":1146},[1086,48303,787],{"class":1105},[1086,48305,1410],{"class":1146},[1086,48307,48308],{"class":1146}," //",[1086,48310,48311],{"class":1187}," 2\n",[1086,48313,48314,48317,48319,48322,48324,48327],{"class":1088,"line":1181},[1086,48315,48316],{"class":1436},"    ascending_only ",[1086,48318,1440],{"class":1146},[1086,48320,48321],{"class":1436}," audio",[1086,48323,29247],{"class":1146},[1086,48325,48326],{"class":1436},"half_point",[1086,48328,1273],{"class":1146},[1086,48330,48331,48334,48336,48338,48340,48342,48344,48347,48349,48351,48353,48355],{"class":1088,"line":1205},[1086,48332,48333],{"class":1436},"    ascending_only",[1086,48335,861],{"class":1146},[1086,48337,3625],{"class":1105},[1086,48339,1398],{"class":1146},[1086,48341,35660],{"class":1105},[1086,48343,1227],{"class":1146},[1086,48345,48346],{"class":1401}," format",[1086,48348,1440],{"class":1146},[1086,48350,1159],{"class":1146},[1086,48352,8982],{"class":1096},[1086,48354,1159],{"class":1146},[1086,48356,1455],{"class":1146},[842,48358,48359,48362],{},[996,48360,48361],{},"Trim chord arpeggios"," - Some chords had unwanted arpeggio intros. FFmpeg to the rescue:",[1013,48364,48366],{"className":1080,"code":48365,"language":1082,"meta":728,"style":728},"ffmpeg -y -i chord.mp3 -ss [start_time] -acodec libmp3lame chord_trimmed.mp3\n",[895,48367,48368],{"__ignoreMap":728},[1086,48369,48370,48372,48374,48376,48379,48382],{"class":1088,"line":1089},[1086,48371,28052],{"class":1092},[1086,48373,42160],{"class":1096},[1086,48375,28132],{"class":1096},[1086,48377,48378],{"class":1096}," chord.mp3",[1086,48380,48381],{"class":1096}," -ss",[1086,48383,48384],{"class":1436}," [start_time] -acodec libmp3lame chord_trimmed.mp3\n",[842,48386,48387,48390],{},[996,48388,48389],{},"Batch update SVG colors"," - Our chord diagrams needed color adjustments to match the app theme. Regex-based batch processing:",[1013,48392,48394],{"className":1368,"code":48393,"language":1250,"meta":728,"style":728},"COLOR_REPLACEMENTS = {\n    '#f3f8f3': '#000000',  # Inactive keys: light → dark\n    '#b3cc57': '#C6F222',  # Active keys: old green → MTL lime\n}\n",[895,48395,48396,48405,48429,48452],{"__ignoreMap":728},[1086,48397,48398,48401,48403],{"class":1088,"line":1089},[1086,48399,48400],{"class":1436},"COLOR_REPLACEMENTS ",[1086,48402,1440],{"class":1146},[1086,48404,1164],{"class":1146},[1086,48406,48407,48410,48413,48415,48417,48419,48422,48424,48426],{"class":1088,"line":729},[1086,48408,48409],{"class":1146},"    '",[1086,48411,48412],{"class":1096},"#f3f8f3",[1086,48414,10742],{"class":1146},[1086,48416,1133],{"class":1146},[1086,48418,26405],{"class":1146},[1086,48420,48421],{"class":1096},"#000000",[1086,48423,10742],{"class":1146},[1086,48425,1227],{"class":1146},[1086,48427,48428],{"class":1427},"  # Inactive keys: light → dark\n",[1086,48430,48431,48433,48436,48438,48440,48442,48445,48447,48449],{"class":1088,"line":1112},[1086,48432,48409],{"class":1146},[1086,48434,48435],{"class":1096},"#b3cc57",[1086,48437,10742],{"class":1146},[1086,48439,1133],{"class":1146},[1086,48441,26405],{"class":1146},[1086,48443,48444],{"class":1096},"#C6F222",[1086,48446,10742],{"class":1146},[1086,48448,1227],{"class":1146},[1086,48450,48451],{"class":1427},"  # Active keys: old green → MTL lime\n",[1086,48453,48454],{"class":1088,"line":1181},[1086,48455,1291],{"class":1146},[1074,48457,48459],{"id":48458},"the-numbers","The Numbers",[842,48461,48462],{},"By the end, our content pipeline generated:",[871,48464,48465,48477],{},[874,48466,48467],{},[877,48468,48469,48472,48475],{},[880,48470,48471],{},"Category",[880,48473,48474],{},"Files",[880,48476,25716],{},[887,48478,48479,48490,48500,48510,48521,48532],{},[877,48480,48481,48484,48487],{},[892,48482,48483],{},"Chords",[892,48485,48486],{},"168 audio + 168 SVG",[892,48488,48489],{},"MIDI → FluidSynth → FFmpeg",[877,48491,48492,48495,48498],{},[892,48493,48494],{},"Scales",[892,48496,48497],{},"14 audio + 14 SVG",[892,48499,48489],{},[877,48501,48502,48504,48507],{},[892,48503,2719],{},[892,48505,48506],{},"8 audio + 8 SVG",[892,48508,48509],{},"pydub sine wave synthesis",[877,48511,48512,48515,48518],{},[892,48513,48514],{},"Rhythms",[892,48516,48517],{},"8 audio + 8 PNG",[892,48519,48520],{},"Mathematical drum synthesis",[877,48522,48523,48526,48529],{},[892,48524,48525],{},"Animals",[892,48527,48528],{},"18 audio + 18 PNG",[892,48530,48531],{},"Curated library",[877,48533,48534,48537,48539],{},[892,48535,48536],{},"Instruments",[892,48538,48517],{},[892,48540,48541],{},"Curated samples",[842,48543,48544,48547],{},[996,48545,48546],{},"Total: 300+ assets",", mostly generated programmatically.",[1074,48549,48550],{"id":14447},"Why This Matters",[842,48552,48553],{},"Could we have licensed a chord library? Sure. But:",[991,48555,48556,48562,48568,48574],{},[961,48557,48558,48561],{},[996,48559,48560],{},"Consistency"," - Every chord sounds identical in timbre, velocity, duration",[961,48563,48564,48567],{},[996,48565,48566],{},"Customization"," - Need a longer sustain? Change one variable, regenerate",[961,48569,48570,48573],{},[996,48571,48572],{},"No licensing headaches"," - We own every bit of audio",[961,48575,48576,48579],{},[996,48577,48578],{},"Educational value"," - We actually understand what we're teaching",[842,48581,48582,48583,861],{},"Plus, writing a drum synthesizer from scratch is just ",[964,48584,48585],{},"fun",[4937,48587],{},[863,48589,48591],{"id":48590},"the-categories-more-than-just-music","The Categories: More Than Just Music",[842,48593,48594],{},"While we started with music education in mind, we quickly realized the concept works for much more.",[1074,48596,48598],{"id":48597},"musical-categories","Musical Categories",[1045,48600,31345,48604,48609,48610],{"className":48601},[13033,46966,48602,48603],"gap-8","items-start",[1027,48605],{"src":48606,"alt":48607,"className":48608},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_3.webp","Category selection",[46977],"\n   ",[1027,48611],{"src":48612,"alt":48613,"className":48614},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_4.webp","Chord diagram",[46977],[842,48616,48617,48619],{},[996,48618,48483],{}," - 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.",[842,48621,48622,48624],{},[996,48623,48494],{}," - From the bright C Major to the melancholic A Natural Minor, players learn to identify scales by their unique character.",[842,48626,48627,48629],{},[996,48628,2719],{}," - Perfect for beginners learning to identify individual pitches on the musical staff.",[842,48631,48632,48634],{},[996,48633,48514],{}," - 3/4 waltz? 4/4 rock beat? Syncopated funk? Train your rhythmic ear.",[1074,48636,48638],{"id":48637},"beyond-music","Beyond Music",[842,48640,48641,48643],{},[996,48642,48525],{}," - 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.",[842,48645,48646,48648],{},[996,48647,48536],{}," - Can you distinguish a trumpet from a saxophone? A violin from a cello? Harder than you'd think!",[4937,48650],{},[863,48652,48654],{"id":48653},"the-game-modes-flexibility-matters","The Game Modes: Flexibility Matters",[842,48656,48657],{},"Not everyone learns the same way. That's why MemoSonic offers two distinct game modes:",[1074,48659,48661],{"id":48660},"memosonic-mode-sound-first","Memosonic Mode (Sound-First)",[842,48663,48664],{},"This is the heart of the app. Tap a card, hear a sound. Remember that sound. Find its match.",[1013,48666,48669],{"className":48667,"code":48668,"language":1018},[1016],"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",[895,48670,48668],{"__ignoreMap":728},[1045,48672,31345,48674,31345,48677,31345,48682],{"className":48673},[13033,46966,46967,13034],[1045,48675],{"className":48676},[46971],[1027,48678],{"src":48679,"alt":48680,"className":48681},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_5.webp","Memosonic mode",[46977],[1045,48683],{"className":48684},[46971],[1074,48686,48688],{"id":48687},"memo-classic-mode-visual-first","Memo Classic Mode (Visual-First)",[842,48690,48691],{},"For those who want a traditional experience, or as a comparison to understand how much harder audio matching is!",[1013,48693,48696],{"className":48694,"code":48695,"language":1018},[1016],"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",[895,48697,48695],{"__ignoreMap":728},[4937,48699],{},[863,48701,48703],{"id":48702},"difficulty-levels-progressive-challenge","Difficulty Levels: Progressive Challenge",[842,48705,48706],{},"We designed three difficulty levels, each carefully balanced:",[871,48708,48709,48725],{},[874,48710,48711],{},[877,48712,48713,48716,48719,48722],{},[880,48714,48715],{},"Level",[880,48717,48718],{},"Cards",[880,48720,48721],{},"Pairs",[880,48723,48724],{},"Estimated Time",[887,48726,48727,48739,48751],{},[877,48728,48729,48732,48734,48736],{},[892,48730,48731],{},"Easy",[892,48733,7970],{},[892,48735,7948],{},[892,48737,48738],{},"2-3 minutes",[877,48740,48741,48744,48746,48748],{},[892,48742,48743],{},"Normal",[892,48745,2522],{},[892,48747,7970],{},[892,48749,48750],{},"5-7 minutes",[877,48752,48753,48756,48759,48761],{},[892,48754,48755],{},"Hard",[892,48757,48758],{},"20",[892,48760,7284],{},[892,48762,48763],{},"10-15 minutes",[1045,48765,31345,48767,31345,48770,31345,48775],{"className":48766},[13033,46966,46967,13034],[1045,48768],{"className":48769},[46971],[1027,48771],{"src":48772,"alt":48773,"className":48774},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_7.webp","Level selection",[46977],[1045,48776],{"className":48777},[46971],[842,48779,48780],{},"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.",[842,48782,48783],{},"At 20 cards? It's a real workout.",[4937,48785],{},[863,48787,48789],{"id":48788},"accessibility-designing-for-everyone","Accessibility: Designing for Everyone",[842,48791,48792],{},"Here's where it gets important.",[1074,48794,48796],{"id":48795},"the-visually-impaired-perspective","The Visually Impaired Perspective",[842,48798,48799],{},"Traditional memory games are impossible for blind or visually impaired players. The entire mechanic relies on seeing and remembering visual positions.",[842,48801,48802],{},[996,48803,48804],{},"MemoSonic flips this on its head.",[842,48806,48807],{},"In Memosonic mode, vision is secondary. You're listening, remembering sounds, matching audio. A visually impaired player can:",[991,48809,48810,48813,48816,48819],{},[961,48811,48812],{},"Navigate the grid using screen reader or spatial memory",[961,48814,48815],{},"Tap cards to hear sounds",[961,48817,48818],{},"Match based purely on audio memory",[961,48820,48821],{},"Receive audio feedback on success/failure",[1045,48823,31345,48825,31345,48828,31345,48833],{"className":48824},[13033,46966,46967,13034],[1045,48826],{"className":48827},[46971],[1027,48829],{"src":48830,"alt":48831,"className":48832},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_8.webp","Game completion",[46977],[1045,48834],{"className":48835},[46971],[1074,48837,48839],{"id":48838},"design-decisions-for-accessibility","Design Decisions for Accessibility",[842,48841,48842],{},"We made several deliberate choices:",[842,48844,48845],{},[996,48846,48847],{},"High Contrast UI",[958,48849,48850,48853,48856,48859],{},[961,48851,48852],{},"Dark background (#0E0F11)",[961,48854,48855],{},"Bright lime yellow accents (#C6F222)",[961,48857,48858],{},"Clear white text",[961,48860,48861],{},"No reliance on color alone for meaning",[842,48863,48864],{},[996,48865,48866],{},"Large Touch Targets",[958,48868,48869,48872,48875],{},[961,48870,48871],{},"Cards are generously sized",[961,48873,48874],{},"Buttons have ample padding",[961,48876,48877],{},"No precision tapping required",[842,48879,48880],{},[996,48881,48882],{},"Audio Feedback",[958,48884,48885,48888,48891],{},[961,48886,48887],{},"Every interaction has sound",[961,48889,48890],{},"Success/failure clearly distinguishable",[961,48892,48893],{},"No silent failures",[842,48895,48896],{},[996,48897,48898],{},"Simple Navigation",[958,48900,48901,48904,48907],{},[961,48902,48903],{},"Linear flow: Home → Category → Level → Game",[961,48905,48906],{},"Back button always available",[961,48908,48909],{},"No complex gestures required",[4937,48911],{},[863,48913,48915],{"id":48914},"the-technical-deep-dive-for-fellow-developers","The Technical Deep Dive (For Fellow Developers)",[1074,48917,48919],{"id":48918},"project-architecture","Project Architecture",[1013,48921,48924],{"className":48922,"code":48923,"language":1018},[1016],"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",[895,48925,48923],{"__ignoreMap":728},[1074,48927,48929],{"id":48928},"key-dependencies","Key Dependencies",[1013,48931,48933],{"className":15447,"code":48932,"language":15449,"meta":728,"style":728},"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",[895,48934,48935,48942,48955,48968,48981,48994,49007],{"__ignoreMap":728},[1086,48936,48937,48940],{"class":1088,"line":1089},[1086,48938,48939],{"class":4109},"dependencies",[1086,48941,1418],{"class":1146},[1086,48943,48944,48947,48949,48952],{"class":1088,"line":729},[1086,48945,48946],{"class":4109},"  flutter_riverpod",[1086,48948,1133],{"class":1146},[1086,48950,48951],{"class":1096}," ^3.0.1",[1086,48953,48954],{"class":1427},"    # State management\n",[1086,48956,48957,48960,48962,48965],{"class":1088,"line":1112},[1086,48958,48959],{"class":4109},"  go_router",[1086,48961,1133],{"class":1146},[1086,48963,48964],{"class":1096}," ^16.3.0",[1086,48966,48967],{"class":1427},"          # Navigation\n",[1086,48969,48970,48973,48975,48978],{"class":1088,"line":1181},[1086,48971,48972],{"class":4109},"  just_audio",[1086,48974,1133],{"class":1146},[1086,48976,48977],{"class":1096}," ^0.10.5",[1086,48979,48980],{"class":1427},"         # Primary audio engine\n",[1086,48982,48983,48986,48988,48991],{"class":1088,"line":1205},[1086,48984,48985],{"class":4109},"  audioplayers",[1086,48987,1133],{"class":1146},[1086,48989,48990],{"class":1096}," ^6.1.0",[1086,48992,48993],{"class":1427},"        # Alternative audio playback\n",[1086,48995,48996,48999,49001,49004],{"class":1088,"line":1276},[1086,48997,48998],{"class":4109},"  flutter_svg",[1086,49000,1133],{"class":1146},[1086,49002,49003],{"class":1096}," ^2.0.10",[1086,49005,49006],{"class":1427},"        # SVG rendering for diagrams\n",[1086,49008,49009,49012,49014,49017],{"class":1088,"line":1282},[1086,49010,49011],{"class":4109},"  shared_preferences",[1086,49013,1133],{"class":1146},[1086,49015,49016],{"class":1096}," ^2.5.3",[1086,49018,49019],{"class":1427},"  # Local settings storage\n",[1074,49021,49023],{"id":49022},"the-card-flip-animation","The Card Flip Animation",[842,49025,49026],{},"One of the most satisfying parts of the app is the card flip animation. Here's the approach:",[1013,49028,49030],{"className":43489,"code":49029,"language":43491,"meta":728,"style":728},"// 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",[895,49031,49032,49037,49044,49056,49071,49094,49116,49120,49129,49146,49172,49184,49201,49227,49234,49240],{"__ignoreMap":728},[1086,49033,49034],{"class":1088,"line":1089},[1086,49035,49036],{"class":1427},"// Simplified card flip logic\n",[1086,49038,49039,49042],{"class":1088,"line":729},[1086,49040,49041],{"class":1092},"AnimatedBuilder",[1086,49043,4094],{"class":1436},[1086,49045,49046,49049,49051,49054],{"class":1088,"line":1112},[1086,49047,49048],{"class":1436},"  animation",[1086,49050,1133],{"class":1146},[1086,49052,49053],{"class":1436}," _flipAnimation",[1086,49055,1202],{"class":1146},[1086,49057,49058,49061,49063,49066,49068],{"class":1088,"line":1181},[1086,49059,49060],{"class":1436},"  builder",[1086,49062,1133],{"class":1146},[1086,49064,49065],{"class":1436}," (context",[1086,49067,1227],{"class":1146},[1086,49069,49070],{"class":1436}," child) {\n",[1086,49072,49073,49076,49079,49081,49083,49085,49087,49089,49092],{"class":1088,"line":1205},[1086,49074,49075],{"class":1155},"    final",[1086,49077,49078],{"class":1436}," angle ",[1086,49080,1440],{"class":1146},[1086,49082,49053],{"class":1436},[1086,49084,861],{"class":1146},[1086,49086,46838],{"class":1436},[1086,49088,2775],{"class":1146},[1086,49090,49091],{"class":1436}," pi",[1086,49093,33466],{"class":1146},[1086,49095,49096,49098,49101,49103,49105,49107,49110,49112,49114],{"class":1088,"line":1276},[1086,49097,49075],{"class":1155},[1086,49099,49100],{"class":1436}," isFront ",[1086,49102,1440],{"class":1146},[1086,49104,49078],{"class":1436},[1086,49106,11164],{"class":1146},[1086,49108,49109],{"class":1436}," pi ",[1086,49111,23036],{"class":1146},[1086,49113,9048],{"class":1187},[1086,49115,33466],{"class":1146},[1086,49117,49118],{"class":1088,"line":1282},[1086,49119,3390],{"emptyLinePlaceholder":738},[1086,49121,49122,49124,49127],{"class":1088,"line":1288},[1086,49123,1460],{"class":1423},[1086,49125,49126],{"class":1092}," Transform",[1086,49128,4094],{"class":1436},[1086,49130,49131,49134,49136,49139,49141,49144],{"class":1088,"line":2685},[1086,49132,49133],{"class":1436},"      transform",[1086,49135,1133],{"class":1146},[1086,49137,49138],{"class":1092}," Matrix4",[1086,49140,861],{"class":1146},[1086,49142,49143],{"class":1105},"identity",[1086,49145,1387],{"class":1436},[1086,49147,49148,49150,49153,49155,49157,49159,49161,49163,49166,49169],{"class":1088,"line":2700},[1086,49149,43594],{"class":1146},[1086,49151,49152],{"class":1105},"setEntry",[1086,49154,1398],{"class":1436},[1086,49156,7948],{"class":1187},[1086,49158,1227],{"class":1146},[1086,49160,9048],{"class":1187},[1086,49162,1227],{"class":1146},[1086,49164,49165],{"class":1187}," 0.001",[1086,49167,49168],{"class":1436},")  ",[1086,49170,49171],{"class":1427},"// Perspective\n",[1086,49173,49174,49176,49179,49182],{"class":1088,"line":3398},[1086,49175,43594],{"class":1146},[1086,49177,49178],{"class":1105},"rotateY",[1086,49180,49181],{"class":1436},"(angle)",[1086,49183,1202],{"class":1146},[1086,49185,49186,49189,49191,49194,49196,49199],{"class":1088,"line":1715},[1086,49187,49188],{"class":1436},"      alignment",[1086,49190,1133],{"class":1146},[1086,49192,49193],{"class":1092}," Alignment",[1086,49195,861],{"class":1146},[1086,49197,49198],{"class":1436},"center",[1086,49200,1202],{"class":1146},[1086,49202,49203,49206,49208,49210,49212,49215,49218,49220,49223,49225],{"class":1088,"line":3409},[1086,49204,49205],{"class":1436},"      child",[1086,49207,1133],{"class":1146},[1086,49209,49100],{"class":1436},[1086,49211,38405],{"class":1146},[1086,49213,49214],{"class":1105}," _buildFrontFace",[1086,49216,49217],{"class":1436},"() ",[1086,49219,1133],{"class":1146},[1086,49221,49222],{"class":1105}," _buildBackFace",[1086,49224,2516],{"class":1436},[1086,49226,1202],{"class":1146},[1086,49228,49229,49232],{"class":1088,"line":3415},[1086,49230,49231],{"class":1436},"    )",[1086,49233,33466],{"class":1146},[1086,49235,49236,49238],{"class":1088,"line":3421},[1086,49237,4797],{"class":1436},[1086,49239,1202],{"class":1146},[1086,49241,49242],{"class":1088,"line":3427},[1086,49243,1455],{"class":1436},[842,49245,49246,49247,49250],{},"The trick is the perspective transform (",[895,49248,49249],{},"setEntry(3, 2, 0.001)",") - it gives that satisfying 3D effect without being distracting.",[4937,49252],{},[863,49254,49255],{"id":25635},"Lessons Learned",[1074,49257,49259],{"id":49258},"_1-audio-latency-is-everything","1. Audio Latency is Everything",[842,49261,49262],{},"In a sound-based game, even tiny delays feel wrong. We spent weeks optimizing audio playback to ensure instant response.",[1074,49264,49266],{"id":49265},"_2-kids-are-brutally-honest-testers","2. Kids Are Brutally Honest Testers",[842,49268,49269],{},"Our first playtest with actual children revealed:",[958,49271,49272,49275,49278],{},[961,49273,49274],{},"\"Why is this taking so long?\" (loading screen was 2 seconds)",[961,49276,49277],{},"\"I already heard that one!\" (audio caching issue)",[961,49279,49280],{},"\"This is too easy!\" (we added Hard mode)",[1074,49282,49284],{"id":49283},"_3-accessibility-isnt-an-afterthought","3. Accessibility Isn't an Afterthought",[842,49286,49287],{},"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.",[1074,49289,49291],{"id":49290},"_4-simple-beats-complex","4. Simple Beats Complex",[842,49293,49294],{},"Our first design had:",[958,49296,49297,49299,49302,49305],{},[961,49298,27824],{},[961,49300,49301],{},"Leaderboards",[961,49303,49304],{},"Achievement systems",[961,49306,49307],{},"Daily challenges",[842,49309,49310],{},"We cut all of it. The core experience - match sounds, train your ear - didn't need any of that. It needed to work flawlessly.",[4937,49312],{},[863,49314,49316],{"id":49315},"whats-next-you-tell-us","What's Next? You Tell Us!",[842,49318,49319,49320,861],{},"We have a bunch of ideas brewing for the next iteration. But here's the thing - ",[996,49321,49322],{},"we'd rather build what you actually want",[842,49324,49325],{},"Take a look at what we're considering:",[842,49327,49328],{},[996,49329,49330],{},"More Categories",[958,49332,49333,49336,49339],{},[961,49334,49335],{},"🐦 Bird songs (nature education)",[961,49337,49338],{},"🌍 World languages (basic vocabulary)",[961,49340,49341],{},"🎼 Famous melodies (classical music education)",[842,49343,49344],{},[996,49345,49346],{},"Enhanced Accessibility",[958,49348,49349,49352,49355],{},[961,49350,49351],{},"🔊 Full VoiceOver/TalkBack support",[961,49353,49354],{},"📳 Haptic feedback for matches",[961,49356,49357],{},"🎧 Audio descriptions for all UI elements",[842,49359,49360],{},[996,49361,49362],{},"Multiplayer Mode",[958,49364,49365,49368,49371],{},[961,49366,49367],{},"👥 Turn-based competition",[961,49369,49370],{},"⚡ Who can match faster?",[961,49372,49373],{},"🏠 Family game night feature",[842,49375,49376],{},[996,49377,49378],{},"Something else entirely?",[842,49380,49381],{},"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.",[842,49383,49384],{},[996,49385,49386],{},"We're listening.",[842,49388,49389,49390,49394],{},"Drop us a line at ",[846,49391,49393],{"href":49392},"mailto:support@musictechlab.io","support@musictechlab.io"," and tell us:",[958,49396,49397,49400,49403],{},[961,49398,49399],{},"Which feature would you use most?",[961,49401,49402],{},"What's missing that would make MemoSonic perfect for you?",[961,49404,49405],{},"Any category ideas we haven't thought of?",[842,49407,49408],{},"The best features come from real users with real needs. Don't be shy - your idea might end up in the next update.",[4937,49410],{},[863,49412,49414],{"id":49413},"the-dream-memosonic-as-a-physical-toy","The Dream: MemoSonic as a Physical Toy",[842,49416,49417],{},"Here's an idea that won't leave our heads...",[842,49419,49420],{},[996,49421,49422],{},"What if MemoSonic wasn't just an app, but a physical toy you could hold in your hands?",[1045,49424,31345,49426,31345,49429,31345,49434],{"className":49425},[13033,46966,46967,13034],[1045,49427],{"className":49428},[46971],[1027,49430],{"src":49431,"alt":49432,"className":49433},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_9.webp","Hardware concept",[46977],[1045,49435],{"className":49436},[46971],[1074,49438,49440],{"id":49439},"the-vision","The Vision",[842,49442,49443],{},"Picture a compact device with a grid of large, tactile buttons. Each button:",[958,49445,49446,49449,49452,49455],{},[961,49447,49448],{},"Lights up with RGB LEDs",[961,49450,49451],{},"Plays a sound when pressed",[961,49453,49454],{},"Has a satisfying click",[961,49456,49457],{},"Is large enough for small hands (and accessible for everyone)",[842,49459,49460],{},"An 8×8 matrix would give us 64 buttons - enough for complex games while keeping each button big enough to press comfortably:",[1013,49462,49465],{"className":49463,"code":49464,"language":1018},[1016],"[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n",[895,49466,49464],{"__ignoreMap":728},[842,49468,49469],{},"Easy mode? Use a 3×2 section. Hard mode? The whole grid lights up.",[1074,49471,49473],{"id":49472},"why-does-this-idea-excite-us","Why Does This Idea Excite Us?",[842,49475,49476,49479],{},[996,49477,49478],{},"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.",[842,49481,49482,49485,49486,49489],{},[996,49483,49484],{},"True tactile accessibility."," For visually impaired users, physical buttons in fixed positions are ",[964,49487,49488],{},"far"," easier to navigate than a touchscreen. You can feel your way around. Build muscle memory. No screen reader required.",[842,49491,49492,49495],{},[996,49493,49494],{},"Multiplayer without devices."," Gather around the table. Take turns. Compete. No \"pass the phone\" awkwardness.",[842,49497,49498,49501],{},[996,49499,49500],{},"Durability."," Kids are rough. A well-designed hardware toy can survive drops, spills, and sibling conflicts.",[1074,49503,49505],{"id":49504},"what-it-could-look-like-technically","What It Could Look Like Technically",[842,49507,49508],{},"If we were to build this, we'd probably explore:",[958,49510,49511,49517,49523,49529,49535,49541],{},[961,49512,49513,49516],{},[996,49514,49515],{},"ESP32 or Raspberry Pi Pico"," as the brain",[961,49518,49519,49522],{},[996,49520,49521],{},"I2S audio"," for quality sound output",[961,49524,49525,49528],{},[996,49526,49527],{},"NeoPixel/WS2812B"," LEDs for button illumination",[961,49530,49531,49534],{},[996,49532,49533],{},"Rechargeable battery"," with USB-C charging",[961,49536,49537,49540],{},[996,49538,49539],{},"SD card slot"," for custom sound packs",[961,49542,49543,49546],{},[996,49544,49545],{},"Bluetooth LE"," for optional app companion (stats, new sounds)",[842,49548,49549],{},"The app and hardware could even sync - unlock new sounds on the device by mastering them in the app first.",[1074,49551,49553],{"id":49552},"the-accessibility-angle","The Accessibility Angle",[842,49555,49556],{},"This is where it gets really exciting.",[842,49558,49559],{},"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.",[842,49561,49562],{},"We imagine:",[958,49564,49565,49571,49577,49583],{},[961,49566,49567,49570],{},[996,49568,49569],{},"Raised symbols"," on each button for tactile identification",[961,49572,49573,49576],{},[996,49574,49575],{},"Audio position announcements"," (\"Button 3\" when pressed)",[961,49578,49579,49582],{},[996,49580,49581],{},"Haptic feedback"," for matches and mismatches",[961,49584,49585,49588],{},[996,49586,49587],{},"Braille labeling"," on the device",[1074,49590,49592],{"id":49591},"is-this-something-youd-want","Is This Something You'd Want?",[842,49594,49595],{},"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.",[842,49597,49598],{},[996,49599,49600],{},"But we could, if there's real interest.",[842,49602,49603],{},"Would you buy a physical MemoSonic for your kids? For a classroom? For a visually impaired family member? Would you back it on Kickstarter?",[842,49605,49606,49609],{},[996,49607,49608],{},"Let us know."," If enough people say \"yes, build this thing!\" - we just might.",[842,49611,49612,49613,49615],{},"📧 ",[846,49614,49393],{"href":49392}," - subject line: \"Hardware MemoSonic\"",[842,49617,49618],{},[964,49619,49620],{},"If the response is strong enough, we'll start prototyping and document the entire journey. Stay tuned.",[4937,49622],{},[863,49624,27843],{"id":27842},[842,49626,49627],{},"MemoSonic is available now:",[958,49629,49630,49640,49650],{},[961,49631,49632,27851,49635],{},[996,49633,49634],{},"Web",[846,49636,49639],{"href":49637,"rel":49638},"https://memosonic.musictechlab.io/",[850],"memosonic.musictechlab.io",[961,49641,49642,27851,49645],{},[996,49643,49644],{},"iOS",[846,49646,49649],{"href":49647,"rel":49648},"https://apps.apple.com/pl/app/memosonic/id6743499949",[850],"App Store",[961,49651,49652,27851,49655],{},[996,49653,49654],{},"Android",[846,49656,49659],{"href":49657,"rel":49658},"https://play.google.com/store/apps/details?id=com.memosonic.app",[850],"Google Play",[842,49661,49662],{},"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.",[4937,49664],{},[863,49666,49668],{"id":49667},"the-bigger-picture","The Bigger Picture",[842,49670,49671],{},"MemoSonic started as a rainy day experiment. It became something more - a proof that:",[991,49673,49674,49680,49686,49692],{},[961,49675,49676,49679],{},[996,49677,49678],{},"Learning doesn't have to look like learning."," The best educational tools feel like play.",[961,49681,49682,49685],{},[996,49683,49684],{},"Accessibility opens doors."," By designing for sound-first gameplay, we accidentally created something that works for people we hadn't initially considered.",[961,49687,49688,49691],{},[996,49689,49690],{},"Simple ideas can have big impact."," Flip cards, match sounds. That's it. But the applications - music education, auditory training, inclusive gaming - are vast.",[961,49693,49694,49697],{},[996,49695,49696],{},"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.",[842,49699,49700],{},"Sometimes the best projects come from playing with your kids.",[4937,49702],{},[863,49704,18693],{"id":18692},[958,49706,49707,49714,49721,49728],{},[961,49708,49709],{},[846,49710,49713],{"href":49711,"rel":49712},"https://flutter.dev",[850],"Flutter Documentation",[961,49715,49716],{},[846,49717,49720],{"href":49718,"rel":49719},"https://pub.dev/packages/just_audio",[850],"just_audio Package",[961,49722,49723],{},[846,49724,49727],{"href":49725,"rel":49726},"https://www.w3.org/WAI/standards-guidelines/mobile/",[850],"Accessibility Guidelines for Mobile Apps",[961,49729,49730],{},[846,49731,49734],{"href":49732,"rel":49733},"https://docs.espressif.com/projects/esp-adf/en/latest/",[850],"ESP32 Audio Projects",[4937,49736],{},[863,49738,49740],{"id":49739},"for-clients-what-to-know-before-commissioning-a-mobile-app","For Clients: What to Know Before Commissioning a Mobile App",[842,49742,49743],{},"We thought it might be useful to share some honest insights from building MemoSonic - especially if you're considering commissioning a mobile app yourself.",[1074,49745,49747],{"id":49746},"timeline-reality","Timeline Reality",[842,49749,49750,49753,49754,861],{},[996,49751,49752],{},"MemoSonic from idea to production:"," ~10 calendar months, but effectively ",[996,49755,49756],{},"2-3 weeks of intense work",[842,49758,49759],{},"Why the difference? Because projects have their own rhythm - there are pauses for testing ideas, gathering feedback, handling other priorities. Realistically:",[871,49761,49762,49771],{},[874,49763,49764],{},[877,49765,49766,49768],{},[880,49767,44336],{},[880,49769,49770],{},"Time",[887,49772,49773,49781,49789,49796,49804],{},[877,49774,49775,49778],{},[892,49776,49777],{},"Prototype / proof of concept",[892,49779,49780],{},"1-2 days",[877,49782,49783,49786],{},[892,49784,49785],{},"Core functionality",[892,49787,49788],{},"3-5 days",[877,49790,49791,49794],{},[892,49792,49793],{},"UI/UX polish",[892,49795,49788],{},[877,49797,49798,49801],{},[892,49799,49800],{},"Testing, bugs, deployment",[892,49802,49803],{},"2-5 days",[877,49805,49806,49811],{},[892,49807,49808],{},[996,49809,49810],{},"Effective total",[892,49812,49813],{},[996,49814,49815],{},"2-3 weeks",[1074,49817,49819],{"id":49818},"where-do-most-problems-occur","Where Do Most Problems Occur?",[842,49821,49822,49825],{},[996,49823,49824],{},"1. Audio/Media"," - Sounds simple (\"just play a sound\"), but:",[958,49827,49828,49831,49834,49837],{},[961,49829,49830],{},"Playback latency issues",[961,49832,49833],{},"Conflicts when sounds overlap",[961,49835,49836],{},"Differences between iOS and Android behavior",[961,49838,49839],{},"Library bugs (e.g., \"Message responses can be sent only once\")",[842,49841,49842,49845],{},[996,49843,49844],{},"2. iOS Builds"," - Every mobile project confirms this. Certificates, provisioning profiles, App Store review. Android is simpler.",[842,49847,49848,49851],{},[996,49849,49850],{},"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.)",[1074,49853,49855],{"id":49854},"simple-ideas-complex-threads","Simple Ideas → Complex Threads",[842,49857,49858],{},"MemoSonic is \"just\" a memory game with sounds. Sounds like a weekend project, right?",[842,49860,49861],{},"And yet:",[958,49863,49864,49870,49876,49882,49888,49894],{},[961,49865,49866,49869],{},[996,49867,49868],{},"300+ audio files"," to generate (chords, scales, rhythms, animal sounds)",[961,49871,49872,49875],{},[996,49873,49874],{},"Content generation pipeline"," - Python, FluidSynth, FFmpeg, sound synthesis",[961,49877,49878,49881],{},[996,49879,49880],{},"Two game modes"," with different logic",[961,49883,49884,49887],{},[996,49885,49886],{},"Three difficulty levels"," with balancing",[961,49889,49890,49893],{},[996,49891,49892],{},"Accessibility"," - contrast, large buttons, audio feedback",[961,49895,49896,49899],{},[996,49897,49898],{},"Card animations"," with 3D perspective",[842,49901,49902],{},"What seems like a \"simple app\" often requires:",[958,49904,49905,49908,49911,49914],{},[961,49906,49907],{},"Integration with multiple libraries",[961,49909,49910],{},"Handling edge cases",[961,49912,49913],{},"Testing on various devices",[961,49915,49916],{},"Iterations based on user feedback",[1074,49918,49920],{"id":49919},"the-golden-rule","The Golden Rule",[41054,49922,49923],{},[842,49924,49925],{},[996,49926,49927],{},"MVP in 5 days, polishing - endless.",[842,49929,49930],{},"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.",[1074,49932,49933],{"id":4928},"The Bottom Line",[842,49935,49936],{},"If you're planning to build an app, budget for:",[958,49938,49939,49945,49951,49957],{},[961,49940,49941,49944],{},[996,49942,49943],{},"Time:"," 2-3x your initial estimate",[961,49946,49947,49950],{},[996,49948,49949],{},"Complexity:"," Simple features often hide complex implementations",[961,49952,49953,49956],{},[996,49954,49955],{},"Platform quirks:"," iOS, Android and web behave differently",[961,49958,49959,49962],{},[996,49960,49961],{},"Content:"," Creating or licensing assets takes time and money",[842,49964,49965,49966,49969],{},"The silver lining? ",[996,49967,49968],{},"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.",[842,49971,49972],{},"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.",[4937,49974],{},[1074,49976,49978],{"id":49977},"try-memosonic","Try MemoSonic",[958,49980,49981,49989,49997],{},[961,49982,49983,7826,49986],{},[996,49984,49985],{},"Web:",[846,49987,49639],{"href":49637,"rel":49988},[850],[961,49990,49991,7826,49994],{},[996,49992,49993],{},"iOS:",[846,49995,49649],{"href":49647,"rel":49996},[850],[961,49998,49999,7826,50002],{},[996,50000,50001],{},"Android:",[846,50003,49659],{"href":49657,"rel":50004},[850],[1680,50006,50007],{},"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":728,"searchDepth":729,"depth":729,"links":50009},[50010,50011,50014,50018,50026,50030,50034,50035,50039,50044,50050,50051,50058,50059,50060,50061],{"id":46949,"depth":729,"text":46950},{"id":46985,"depth":729,"text":46986,"children":50012},[50013],{"id":47015,"depth":1112,"text":47016},{"id":47067,"depth":729,"text":47068,"children":50015},[50016,50017],{"id":47071,"depth":1112,"text":47072},{"id":47095,"depth":1112,"text":47096},{"id":47143,"depth":729,"text":47144,"children":50019},[50020,50021,50022,50023,50024,50025],{"id":47168,"depth":1112,"text":47169},{"id":47448,"depth":1112,"text":47449},{"id":47996,"depth":1112,"text":47997},{"id":48241,"depth":1112,"text":48242},{"id":48458,"depth":1112,"text":48459},{"id":14447,"depth":1112,"text":48550},{"id":48590,"depth":729,"text":48591,"children":50027},[50028,50029],{"id":48597,"depth":1112,"text":48598},{"id":48637,"depth":1112,"text":48638},{"id":48653,"depth":729,"text":48654,"children":50031},[50032,50033],{"id":48660,"depth":1112,"text":48661},{"id":48687,"depth":1112,"text":48688},{"id":48702,"depth":729,"text":48703},{"id":48788,"depth":729,"text":48789,"children":50036},[50037,50038],{"id":48795,"depth":1112,"text":48796},{"id":48838,"depth":1112,"text":48839},{"id":48914,"depth":729,"text":48915,"children":50040},[50041,50042,50043],{"id":48918,"depth":1112,"text":48919},{"id":48928,"depth":1112,"text":48929},{"id":49022,"depth":1112,"text":49023},{"id":25635,"depth":729,"text":49255,"children":50045},[50046,50047,50048,50049],{"id":49258,"depth":1112,"text":49259},{"id":49265,"depth":1112,"text":49266},{"id":49283,"depth":1112,"text":49284},{"id":49290,"depth":1112,"text":49291},{"id":49315,"depth":729,"text":49316},{"id":49413,"depth":729,"text":49414,"children":50052},[50053,50054,50055,50056,50057],{"id":49439,"depth":1112,"text":49440},{"id":49472,"depth":1112,"text":49473},{"id":49504,"depth":1112,"text":49505},{"id":49552,"depth":1112,"text":49553},{"id":49591,"depth":1112,"text":49592},{"id":27842,"depth":729,"text":27843},{"id":49667,"depth":729,"text":49668},{"id":18692,"depth":729,"text":18693},{"id":49739,"depth":729,"text":49740,"children":50062},[50063,50064,50065,50066,50067,50068],{"id":49746,"depth":1112,"text":49747},{"id":49818,"depth":1112,"text":49819},{"id":49854,"depth":1112,"text":49855},{"id":49919,"depth":1112,"text":49920},{"id":4928,"depth":1112,"text":49933},{"id":49977,"depth":1112,"text":49978},{"name":12840,"logo":50070},"/images/logos/memosonic-app.png","2026-01-08T00:00:00.000Z","The story behind MemoSonic, a Flutter-based educational game that turns sound recognition into play, with accessibility for visually impaired users.",{"src":50074},"/images/blog/musictechlab_blog_how-we-built-memosonic-accessible-audio-memory-game-flutter.webp",{"enabled":738,"items":50076},[50077,50079,50081,50083],{"text":50078,"icon":1723},"300+ audio assets generated programmatically using Python, FluidSynth, and FFmpeg.",{"text":50080,"icon":40852},"Sound-first gameplay makes the app accessible to visually impaired players by design.",{"text":50082,"icon":2939},"Effective development took 2-3 weeks despite a 10-month calendar timeline.",{"text":50084,"icon":9547},"6 categories cover chords, scales, rhythms, notes, animals, and instruments.",{},{"title":26,"description":50072},[4990,18784],"EpQHhZ99WiwpfNwqAZy1Tpb3DU7mhR5z4TDWIwaKj0c",{"id":50090,"title":128,"authors":50091,"badge":50097,"body":50100,"category":731,"client":723,"date":50258,"description":50259,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":50260,"keyTakeaways":50262,"meta":50271,"navigation":738,"path":129,"seo":50272,"status":723,"stem":130,"tags":50273,"teaser":723,"__hash__":50274},"posts/blog/music-data/ddex-office-hours-musictech.md",[50092],{"name":50093,"to":50094,"avatar":50095},"Maciej Dulski","https://www.linkedin.com/in/maciej-dulski/",{"src":50096},"/images/cdn-migrated/maciej-dulski-400x400.webp",{"label":50098,"color":50099},"Featured","#E91E63",{"type":725,"value":50101,"toc":50252},[50102,50108,50119,50126,50130,50149,50152,50156,50159,50176,50181,50185,50192,50206,50209,50212,50227,50235],[842,50103,50104,50105,861],{},"If you're building a MusicTech platform that needs to integrate with labels, distributors, or DSPs, DDEX compliance becomes unavoidable at some point. The challenge is knowing ",[996,50106,50107],{},"what to implement, when, and how much is actually required",[842,50109,50110,50111,50118],{},"MusicTech Lab is a ",[996,50112,50113],{},[846,50114,50117],{"href":50115,"rel":50116},"https://ddex.net/membership/current-members/",[850],"DDEX member",", and these office hours are run by our DDEX expert — based on hands-on experience with production systems used across the music industry.",[842,50120,50121,50122,50125],{},"We offer ",[996,50123,50124],{},"2 free DDEX office hour slots per month",", focused on practical guidance.",[863,50127,50129],{"id":50128},"what-we-can-help-with","What We Can Help With",[1045,50131,50133,50137,50141,50145],{"className":50132},[1048,1049,1765,1051,1052],[1054,50134],{"description":50135,"title":50136},"Choosing the right DDEX standard for your use case — ERN, DSR, RIN, or MLC — and understanding the real-world differences between ERN versions.","Standards Selection",[1054,50138],{"description":50139,"title":50140},"Reviewing your internal data model for DDEX readiness. Mapping Releases, Resources, Deals, and Parties to your existing structures.","Data Model Preparation",[1054,50142],{"description":50143,"title":50144},"Designing ERN export APIs, including OpenAPI-based approaches. Handling identifiers for artists, contributors, and rights holders correctly.","API Design",[1054,50146],{"description":50147,"title":50148},"Common compliance pitfalls, validation strategies, and how to test your DDEX output before sending it to DSPs.","Validation & Compliance",[842,50150,50151],{},"You can bring a specific technical problem or ask us to review your overall approach.",[863,50153,50155],{"id":50154},"common-questions-weve-answered","Common Questions We've Answered",[842,50157,50158],{},"Here are real examples of what teams bring to office hours:",[958,50160,50161,50164,50167,50170,50173],{},[961,50162,50163],{},"\"We have track metadata in our database — how do we map it to ERN 3.8?\"",[961,50165,50166],{},"\"Our distributor is asking for DDEX-compliant delivery. Where do we start?\"",[961,50168,50169],{},"\"We're building an export pipeline. Should we generate XML directly or use an intermediary format?\"",[961,50171,50172],{},"\"How do we handle multi-territory deals and release windows?\"",[961,50174,50175],{},"\"What's the minimum viable ERN file that YouTube/Spotify will accept?\"",[1572,50177,50178],{},[842,50179,50180],{},"These aren't theoretical discussions. Every session is focused on your specific implementation — your data model, your tech stack, your timeline.",[863,50182,50184],{"id":50183},"real-implementation-experience","Real Implementation Experience",[842,50186,50187,50188,50191],{},"We recently supported a music creation platform in preparing their internal data model for ",[996,50189,50190],{},"DDEX ERN-based metadata delivery",". The engagement included:",[958,50193,50194,50197,50200,50203],{},[961,50195,50196],{},"Data model review and gap analysis for DDEX compliance",[961,50198,50199],{},"Mapping internal fields to ERN structures",[961,50201,50202],{},"Defining export specifications for ERN 3.8 with future upgrade paths",[961,50204,50205],{},"Documentation, examples, and validation recommendations",[842,50207,50208],{},"This gave the client a clear, compliant foundation to move toward distributor and DSP integrations.",[863,50210,50211],{"id":9773},"How It Works",[1045,50213,50215,50219,50223],{"className":50214},[1048,1049,1050,1051,1052],[1054,50216],{"description":50217,"title":50218},"First come, first served. Each session is 30–45 minutes.","2 free slots per month",[1054,50220],{"description":50221,"title":50222},"Bring your code, data models, or architecture diagrams. We'll review and advise.","Technical consultation",[1054,50224],{"description":50225,"title":50226},"No sales pitch. Just practical help with real implementation choices.","Focused on decisions",[1901,50228,50229],{},[842,50230,50231,50232,861],{},"These office hours are provided by MusicTech Lab. MusicTech Lab is a DDEX member, but this service is ",[996,50233,50234],{},"not endorsed, operated, or provided by DDEX",[1045,50236,50240,50247],{"className":50237},[13033,50238,50239,1052],"flex-wrap","gap-3",[50241,50242],"u-button",{"color":50243,"label":50244,"target":50245,"to":4946,"variant":50246},"primary","Book a Session","_blank","subtle",[50241,50248],{"color":50249,"label":50250,"target":50245,"to":50251,"variant":50246},"neutral","DDEX Integration Services","https://www.musictechlab.io/lp/ddex-integration",{"title":728,"searchDepth":729,"depth":729,"links":50253},[50254,50255,50256,50257],{"id":50128,"depth":729,"text":50129},{"id":50154,"depth":729,"text":50155},{"id":50183,"depth":729,"text":50184},{"id":9773,"depth":729,"text":50211},"2025-12-22T00:00:00.000Z","Free DDEX office hours for MusicTech teams. Get help with DDEX compliance, ERN implementation, data models, and integrations with DSPs.",{"src":50261},"/images/blog/musictechlab_blog_musictech-lab-ddex-office-hours.webp",{"enabled":738,"items":50263},[50264,50266,50268],{"text":50265,"icon":4845},"MusicTech Lab offers 2 free DDEX office hour slots per month for hands-on technical guidance.",{"text":50267,"icon":31803},"Sessions cover standards selection, data model mapping, API design, and validation strategies.",{"text":50269,"icon":50270},"Every session is focused on your specific implementation, not theoretical discussions.","i-lucide-lightbulb",{},{"title":128,"description":50259},[9763,5523,3857],"fWDpAGfTMZ7V5Tw3xdNZtUeTnuGDsqZSiSZtl-ri2Ms",{"id":50276,"title":285,"authors":50277,"badge":50283,"body":50285,"category":5678,"client":723,"date":50525,"description":5679,"extension":734,"faq":723,"featured":738,"featuredOrder":3526,"hidden":69,"image":50526,"keyTakeaways":723,"meta":50528,"navigation":738,"path":286,"seo":50529,"status":723,"stem":287,"tags":50530,"teaser":723,"__hash__":50531},"posts/blog/newsletter/musictech-insights-6-curated-by-sigurdur-arnason.md",[50278],{"name":50279,"to":50280,"avatar":50281},"Sigurdur Arnason","https://www.linkedin.com/in/sigurdur-arnason-2b690311a/",{"src":50282},"/images/partners/Sigurdur-Arnason-400-400.webp",{"label":50284,"color":5535},"#6",{"type":725,"value":50286,"toc":50522},[50287,50291,50304,50307,50310,50312,50316,50321,50332,50337,50346,50351,50356,50358,50363,50366,50369,50372,50375,50381,50385,50400,50403,50406,50412,50416,50424,50427,50430,50433,50436,50442,50446,50461,50464,50467,50470,50476,50480,50483,50486,50489,50495,50499,50504,50507,50510,50513,50516],[5539,50288,50290],{"id":50289},"musictech-insights-6-curated-by-sigurdur-arnason","MusicTech Insights #6 | Curated by Sigurdur Arnason",[842,50292,50293,50294,50297,50298,50303],{},"This issue is guest-curated by ",[846,50295,50279],{"href":50280,"rel":50296},[850],", CEO and founder of ",[846,50299,50302],{"href":50300,"rel":50301},"https://www.overtune.com/",[850],"Overtune",", a music-making app that lets anyone create music using simple sequencing, samples, and beat packs. In this edition, Sigurdur takes a hard look at AI in music, pushing back against hype and conventional wisdom.",[842,50305,50306],{},"Expect sharp, contrarian insights on why AI isn’t the real crisis, how social media has warped creativity, and what the next cultural paradigm in music could look like.",[842,50308,50309],{},"We’re excited to share Sigurdur’s perspective and his pick of the most relevant stories shaping MusicTech today.",[4937,50311],{},[863,50313,50315],{"id":50314},"the-end-of-an-era-its-all-about-to-crash","The End of an Era: It’s All About to Crash",[842,50317,50318],{},[964,50319,50320],{},"Hi, my name is Sigurdur. Most people call me Siggi.",[842,50322,50323],{},[964,50324,50325,50326,50331],{},"Back in April, ",[846,50327,50330],{"href":50328,"rel":50329},"https://musically.com/2025/04/22/overtune-takes-aim-at-suno-with-new-investors-and-loopgen-ai-feature/",[850],"I commented on Mikey Schulmann’s claim that “no one likes making music anymore",".” Since then, I’ve been asked repeatedly to expand on my views about Suno, Mikey, and that entire ecosystem. But this conversation is much bigger than one founder or one company.",[842,50333,50334],{},[964,50335,50336],{},"What we’re witnessing is not just an AI bubble. It’s the collapse of a cultural paradigm that has defined the last 20 years.",[842,50338,50339],{},[964,50340,50341,50342,50345],{},"With support from MusicTech Lab, and special thanks to ",[846,50343,50093],{"href":50094,"rel":50344},[850]," for research help, I dug into several articles about AI in creative industries. Most arguments fixate on royalties, copyright, or the lack of human touch. These points matter, but they miss the deeper shift.",[842,50347,50348],{},[964,50349,50350],{},"AI isn’t the crisis. AI is the symptom.",[842,50352,50353],{},[964,50354,50355],{},"The real crisis is that the cultural operating system we’ve lived under since the rise of social media is running out of road.",[4937,50357],{},[50359,50360,50362],"h4",{"id":50361},"its-time-to-rage-against-the-ai-music-machine","It’s Time to Rage Against the AI Music Machine",[842,50364,50365],{},"Maciej surfaced this piece, which frames AI as the ultimate fake and highlights public pushback against machine-generated music. But long before AI entered the conversation, artists were already battling the algorithm.",[842,50367,50368],{},"For years, especially in the TikTok era, musicians have begged the algorithm to pick them, hoping virality would validate their work.",[842,50370,50371],{},"Here’s the uncomfortable truth. Social media doesn’t reward creativity or sincerity. It rewards speed, repetition, emotional spikes, and familiarity. AI-generated slop thrives because it is engineered for that system.",[842,50373,50374],{},"Its mechanical scale exposes the emptiness of the current paradigm by taking it to its logical extreme. We’ve sensed something was off for a long time. Now we finally have a villain to point to.",[842,50376,50377],{},[846,50378,5605],{"href":50379,"rel":50380},"https://time.com/7338205/rage-against-ai-generated-music/",[850],[50359,50382,50384],{"id":50383},"bereal-and-the-authenticity-rebellion","BeReal and the Authenticity Rebellion",[842,50386,50387,50388,50393,50394,50399],{},"In a LinkedIn post about ",[846,50389,50392],{"href":50390,"rel":50391},"https://bereal.com/",[850],"BeReal",", its founder ",[846,50395,50398],{"href":50396,"rel":50397},"https://www.linkedin.com/in/bjacobmoore/",[850],"Ben Moore"," explains the platform’s mission as a kind of protest against the fakeness of traditional social media.",[842,50401,50402],{},"But authenticity alone isn’t a business model.",[842,50404,50405],{},"When social media collapses, BeReal will only have a real chance of surviving if it stops positioning itself purely as a social platform and instead evolves with culture, not against it.",[842,50407,50408],{},[846,50409,5605],{"href":50410,"rel":50411},"https://www.linkedin.com/posts/bjacobmoore_bereal-has-officially-become-the-only-social-activity-7397291770780463105-bsxz/",[850],[50359,50413,50415],{"id":50414},"treat-ai-as-a-noisy-environment","Treat AI as a Noisy Environment",[842,50417,50418,50423],{},[846,50419,50422],{"href":50420,"rel":50421},"https://www.linkedin.com/in/virginieberger/",[850],"Virginie Berger"," is one of the sharpest voices in music tech, fighting to protect artists from exploitative AI practices. But here’s the real issue.",[842,50425,50426],{},"Suno signing with Warner or Udio disappearing into Universal is the wrong mental model. It’s like imagining YouTube in 2004 flying to Hollywood to get Paramount to legitimize the platform.",[842,50428,50429],{},"That would have missed the point entirely.",[842,50431,50432],{},"Music doesn’t need validation from the old system. It needs a new grammar.",[842,50434,50435],{},"YouTube used video, but it was never a movie studio. The next music medium will use audio, but it won’t look like the music industry as we know it.",[842,50437,50438],{},[846,50439,5605],{"href":50440,"rel":50441},"https://musically.com/2025/12/08/connect-chats-virginie-berger-treat-ai-as-a-noisy-environment/",[850],[50359,50443,50445],{"id":50444},"amazon-is-blundering-into-an-ai-copyright-nightmare","Amazon Is Blundering Into an AI Copyright Nightmare",[842,50447,50448,50449,50454,50455,50460],{},"This piece pushed me to speak with ",[846,50450,50453],{"href":50451,"rel":50452},"https://www.linkedin.com/in/stuart-dredge/",[850],"Stuart Dredge"," at ",[846,50456,50459],{"href":50457,"rel":50458},"https://musically.com/",[850],"Music Ally"," about the deeper issues behind Mikey’s comments. His line that “musicians are being outnumbered by their audience” is technically true, but context matters.",[842,50462,50463],{},"Today’s paradigm is built on feeds, algorithms, reach, and metrics. Creativity is treated as content. Value is measured by scale. Success becomes a number.",[842,50465,50466],{},"This is why creators feel alienated. Pouring your heart into your craft often gets punished, not rewarded.",[842,50468,50469],{},"My issue with Mikey isn’t morality. It’s shortsightedness. His actions reveal a CEO who is out of his depth and now publicly hiding behind PR damage control.",[842,50471,50472],{},[846,50473,5605],{"href":50474,"rel":50475},"https://www.theverge.com/tech/631651/amazon-alexa-suno-ai-generated-song-copyright-nightmare",[850],[50359,50477,50479],{"id":50478},"disney-is-about-to-embrace-generative-ai-and-the-internet-is-furious","Disney Is About to Embrace Generative AI, and the Internet Is Furious",[842,50481,50482],{},"This isn’t just music. The same pattern is unfolding across all creative industries.",[842,50484,50485],{},"AI slop floods feeds. Trust in AI collapses. Prompt culture peaks. Velocity overtakes meaning. Social feed value flattens. Streaming value flattens.",[842,50487,50488],{},"The cracks are visible everywhere.",[842,50490,50491],{},[846,50492,5605],{"href":50493,"rel":50494},"https://www.forbes.com/sites/danidiplacido/2025/11/16/disney-is-about-to-embrace-generative-ai-and-the-internet-is-furious/",[850],[50359,50496,50498],{"id":50497},"podcasts-a-new-twist-on-net-audio","Podcasts: A New Twist on Net Audio",[842,50500,50501],{},[964,50502,50503],{},"Wired, October 8, 2004",[842,50505,50506],{},"When radio was dominant, a fringe phenomenon appeared. Amateurs talking into cheap microphones in their basements. It looked niche, unscalable, and impossible to monetize.",[842,50508,50509],{},"It became podcasting.",[842,50511,50512],{},"New paradigms always start this way. Small audiences first. Deeper meaning first. Scale later.",[842,50514,50515],{},"Music is the language of emotion. It’s no surprise that creators reject its reduction into disposable audio.",[842,50517,50518],{},[846,50519,5605],{"href":50520,"rel":50521},"https://www.wired.com/2004/10/podcasts-new-twist-on-net-audio/",[850],{"title":728,"searchDepth":729,"depth":729,"links":50523},[50524],{"id":50314,"depth":729,"text":50315},"2025-12-12T00:00:00.000Z",{"src":50527},"/images/blog/musictechlab_blog_musictech-insights_6_sigurdur_arnason.webp",{},{"title":285,"description":5679},[5678,5523],"CaRNp41kBn3vMLkLMFy8SvsHUwPyplcGt3u8-MBpbGw",{"id":50533,"title":546,"authors":50534,"badge":50537,"body":50538,"category":756,"client":723,"date":51057,"description":51058,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":51059,"keyTakeaways":51061,"meta":51069,"navigation":738,"path":547,"seo":51070,"status":723,"stem":548,"tags":51071,"teaser":723,"__hash__":51072},"posts/blog/software-development/integrating-tempus-metronome-with-the-getsongbpm-api-what-bpm-really-means-and-how-to-use-it.md",[50535],{"name":834,"to":720,"avatar":50536},{"src":722},{"label":50098,"color":50099},{"type":725,"value":50539,"toc":51043},[50540,50547,50549,50553,50571,50598,50602,50624,50628,50633,50635,50639,50660,50668,50670,50674,50694,50696,50700,50715,50718,50736,50738,50742,50746,51012,51014,51018,51021,51023,51027,51040],[842,50541,50542,50543,50546],{},"Understanding tempo is one of the fundamentals of music performance, practice, and DJing. In Tempus Metronome, we integrate directly with the ",[996,50544,50545],{},"GetSongBPM API",", allowing users to look up a song, check its tempo, and instantly set the metronome to match. This article explains what BPM actually is, why it matters, where tempo data comes from, and how to integrate GetSongBPM into Flutter — all in one place.",[4937,50548],{},[863,50550,50552],{"id":50551},"what-is-bpm","What Is BPM?",[842,50554,50555,50558,50559,50562,50563,50566,50567,50570],{},[996,50556,50557],{},"BPM (Beats Per Minute)"," describes the tempo of a piece of music — how many beats occur in one minute. A slow ballad might be ",[996,50560,50561],{},"60 BPM",", a pop track ",[996,50564,50565],{},"120 BPM",", and fast electronic music can reach ",[996,50568,50569],{},"150–180 BPM"," or more.",[1045,50572,50576,50580,50584,50589,50594],{"className":50573},[1048,50574,50575,1051,1052],"grid-cols-2","md:grid-cols-5",[1054,50577],{"description":50578,"icon":31808,"title":50579},"Keep a steady beat during practice and performance.","Consistent Timing",[1054,50581],{"description":50582,"icon":11486,"title":50583},"Match tempos between tracks for seamless transitions.","DJ Matching",[1054,50585],{"description":50586,"icon":50587,"title":50588},"Design workouts and running playlists by energy level.","i-lucide-dumbbell","Workout Playlists",[1054,50590],{"description":50591,"icon":50592,"title":50593},"Practice specific rhythmic patterns at precise tempos.","i-lucide-music-4","Rhythmic Practice",[1054,50595],{"description":50596,"icon":3649,"title":50597},"Analyze energy and intensity in any track.","Song Analysis",[1074,50599,50601],{"id":50600},"what-bpm-can-tell-you","What BPM Can Tell You",[1045,50603,50606,50610,50614,50619],{"className":50604},[1048,50574,50605,1051,1052],"md:grid-cols-4",[1054,50607],{"description":50608,"icon":2939,"title":50609},"Higher BPM generally means higher energy.","Energy Level",[1054,50611],{"description":50612,"icon":13608,"title":50613},"Most genres cluster around specific BPM ranges.","Genre Tendencies",[1054,50615],{"description":50616,"icon":50617,"title":50618},"Faster tempos demand more from the performer.","i-lucide-gauge","Difficulty",[1054,50620],{"description":50621,"icon":50622,"title":50623},"Plan warm-ups, drills, and cool-downs by tempo.","i-lucide-list-checks","Training Structure",[1074,50625,50627],{"id":50626},"what-to-watch-out-for","What to Watch Out For",[1901,50629,50630],{},[842,50631,50632],{},"BPM data is not always reliable. Watch out for tempo changes within a track, live recordings with natural drift, different remixes at different speeds, double-time / half-time feel that confuses detection, and incorrect crowd-sourced data.",[4937,50634],{},[863,50636,50638],{"id":50637},"where-can-you-search-for-bpm-data","Where Can You Search for BPM Data?",[1045,50640,50643,50647,50651,50655],{"className":50641},[1048,1049,1765,50642,1051,1052],"lg:grid-cols-4",[1054,50644],{"description":50645,"icon":7560,"title":50646},"Dedicated BPM database with a simple REST API. Used by Tempus Metronome.","GetSongBPM",[1054,50648],{"description":50649,"icon":9547,"title":50650},"Rich audio analysis including tempo, key, and danceability via Spotify's API.","Spotify Audio Features",[1054,50652],{"description":50653,"icon":5315,"title":50654},"Audio recognition services that identify songs and return metadata including BPM.","AudD / ACRCloud",[1054,50656],{"description":50657,"icon":50658,"title":50659},"Tap along to the beat and calculate BPM manually — the most reliable fallback.","i-lucide-hand","Manual Tap-Tempo",[1032,50661,50662],{},[842,50663,50664,50665,50667],{},"Tempus Metronome uses ",[996,50666,50646],{}," for its simplicity and speed. One API call returns the tempo — no OAuth, no complex setup.",[4937,50669],{},[863,50671,50673],{"id":50672},"getting-started-with-the-getsongbpm-api","Getting Started with the GetSongBPM API",[1045,50675,50677,50682,50686,50690],{"className":50676},[1048,1049,1765,1051,1052],[1054,50678],{"description":50679,"icon":50680,"title":50681},"Sign up at getsongbpm.com/api to get your API key. Free tier available.","i-lucide-key","Register",[1054,50683],{"description":50684,"icon":1062,"title":50685},"The API terms require a backlink to GetSongBPM in your app or website.","Backlink Required",[1054,50687],{"description":50688,"icon":3271,"title":50689},"3,000 requests per hour — more than enough for a metronome app.","Rate Limit",[1054,50691],{"description":50692,"icon":1057,"title":50693},"Store your key in environment variables, never hardcode it in source code.","API Key Security",[4937,50695],{},[863,50697,50699],{"id":50698},"environment-configuration","Environment Configuration",[1013,50701,50703],{"className":1080,"code":50702,"language":1082,"meta":728,"style":728},"GETSONGBPM_API_KEY=your_api_key_here\n",[895,50704,50705],{"__ignoreMap":728},[1086,50706,50707,50710,50712],{"class":1088,"line":1089},[1086,50708,50709],{"class":1436},"GETSONGBPM_API_KEY",[1086,50711,1440],{"class":1146},[1086,50713,50714],{"class":1096},"your_api_key_here\n",[842,50716,50717],{},"For Flutter Web, pass the key at build time:",[1013,50719,50721],{"className":1080,"code":50720,"language":1082,"meta":728,"style":728},"flutter build web --dart-define=GETSONGBPM_API_KEY=your_api_key_here\n",[895,50722,50723],{"__ignoreMap":728},[1086,50724,50725,50728,50730,50733],{"class":1088,"line":1089},[1086,50726,50727],{"class":1092},"flutter",[1086,50729,34372],{"class":1096},[1086,50731,50732],{"class":1096}," web",[1086,50734,50735],{"class":1096}," --dart-define=GETSONGBPM_API_KEY=your_api_key_here\n",[4937,50737],{},[863,50739,50741],{"id":50740},"flutter-integration-example","Flutter Integration Example",[1074,50743,50745],{"id":50744},"api-service","API Service",[1013,50747,50749],{"className":43489,"code":50748,"language":43491,"meta":728,"style":728},"class GetSongBpmApi {\n  final String baseUrl = \"https://api.getsongbpm.com\";\n  final String apiKey = dotenv.env['GETSONGBPM_API_KEY']!;\n\n  Future\u003CList\u003Cdynamic>> searchSongs(String query) async {\n    final url = Uri.parse(\n      \"$baseUrl/search/?api_key=$apiKey&type=multi&lookup=$query\"\n    );\n    final response = await http.get(url);\n    if (response.statusCode == 200) {\n      final jsonResponse = jsonDecode(response.body);\n      return jsonResponse['search'] ?? [];\n    } else {\n      throw Exception(\"Failed to load data\");\n    }\n  }\n}\n",[895,50750,50751,50760,50777,50804,50808,50841,50859,50885,50891,50914,50933,50955,50975,50984,51000,51004,51008],{"__ignoreMap":728},[1086,50752,50753,50755,50758],{"class":1088,"line":1089},[1086,50754,4036],{"class":1146},[1086,50756,50757],{"class":1092}," GetSongBpmApi",[1086,50759,1164],{"class":1436},[1086,50761,50762,50764,50767,50770,50772,50775],{"class":1088,"line":729},[1086,50763,43512],{"class":1155},[1086,50765,50766],{"class":1092}," String",[1086,50768,50769],{"class":1436}," baseUrl ",[1086,50771,1440],{"class":1146},[1086,50773,50774],{"class":1096}," \"https://api.getsongbpm.com\"",[1086,50776,33466],{"class":1146},[1086,50778,50779,50781,50783,50786,50788,50791,50793,50796,50799,50801],{"class":1088,"line":1112},[1086,50780,43512],{"class":1155},[1086,50782,50766],{"class":1092},[1086,50784,50785],{"class":1436}," apiKey ",[1086,50787,1440],{"class":1146},[1086,50789,50790],{"class":1436}," dotenv",[1086,50792,861],{"class":1146},[1086,50794,50795],{"class":1436},"env[",[1086,50797,50798],{"class":1096},"'GETSONGBPM_API_KEY'",[1086,50800,4420],{"class":1436},[1086,50802,50803],{"class":1146},"!;\n",[1086,50805,50806],{"class":1088,"line":1181},[1086,50807,3390],{"emptyLinePlaceholder":738},[1086,50809,50810,50813,50815,50818,50820,50823,50826,50829,50831,50834,50837,50839],{"class":1088,"line":1205},[1086,50811,50812],{"class":1092},"  Future",[1086,50814,11164],{"class":1436},[1086,50816,50817],{"class":1092},"List",[1086,50819,11164],{"class":1436},[1086,50821,50822],{"class":1092},"dynamic",[1086,50824,50825],{"class":1436},">> ",[1086,50827,50828],{"class":1105},"searchSongs",[1086,50830,1398],{"class":1436},[1086,50832,50833],{"class":1092},"String",[1086,50835,50836],{"class":1436}," query) ",[1086,50838,26419],{"class":1423},[1086,50840,1164],{"class":1436},[1086,50842,50843,50845,50848,50850,50853,50855,50857],{"class":1088,"line":1276},[1086,50844,49075],{"class":1155},[1086,50846,50847],{"class":1436}," url ",[1086,50849,1440],{"class":1146},[1086,50851,50852],{"class":1092}," Uri",[1086,50854,861],{"class":1146},[1086,50856,33508],{"class":1105},[1086,50858,4094],{"class":1436},[1086,50860,50861,50863,50865,50868,50871,50873,50876,50879,50881,50883],{"class":1088,"line":1282},[1086,50862,1184],{"class":1096},[1086,50864,34155],{"class":1436},[1086,50866,50867],{"class":1401},"baseUrl",[1086,50869,50870],{"class":1096},"/search/?api_key=",[1086,50872,34155],{"class":1436},[1086,50874,50875],{"class":1401},"apiKey",[1086,50877,50878],{"class":1096},"&type=multi&lookup=",[1086,50880,34155],{"class":1436},[1086,50882,7210],{"class":1401},[1086,50884,4441],{"class":1096},[1086,50886,50887,50889],{"class":1088,"line":1288},[1086,50888,49231],{"class":1436},[1086,50890,33466],{"class":1146},[1086,50892,50893,50895,50898,50900,50902,50905,50907,50909,50912],{"class":1088,"line":2685},[1086,50894,49075],{"class":1155},[1086,50896,50897],{"class":1436}," response ",[1086,50899,1440],{"class":1146},[1086,50901,4659],{"class":1423},[1086,50903,50904],{"class":1436}," http",[1086,50906,861],{"class":1146},[1086,50908,10812],{"class":1105},[1086,50910,50911],{"class":1436},"(url)",[1086,50913,33466],{"class":1146},[1086,50915,50916,50918,50921,50923,50926,50928,50930],{"class":1088,"line":2700},[1086,50917,6474],{"class":1423},[1086,50919,50920],{"class":1436}," (response",[1086,50922,861],{"class":1146},[1086,50924,50925],{"class":1436},"statusCode ",[1086,50927,6480],{"class":1146},[1086,50929,22686],{"class":1187},[1086,50931,50932],{"class":1436},") {\n",[1086,50934,50935,50937,50940,50942,50945,50948,50950,50953],{"class":1088,"line":3398},[1086,50936,43579],{"class":1155},[1086,50938,50939],{"class":1436}," jsonResponse ",[1086,50941,1440],{"class":1146},[1086,50943,50944],{"class":1105}," jsonDecode",[1086,50946,50947],{"class":1436},"(response",[1086,50949,861],{"class":1146},[1086,50951,50952],{"class":1436},"body)",[1086,50954,33466],{"class":1146},[1086,50956,50957,50960,50963,50966,50968,50971,50973],{"class":1088,"line":1715},[1086,50958,50959],{"class":1423},"      return",[1086,50961,50962],{"class":1436}," jsonResponse[",[1086,50964,50965],{"class":1096},"'search'",[1086,50967,33549],{"class":1436},[1086,50969,50970],{"class":1146},"??",[1086,50972,38612],{"class":1436},[1086,50974,33466],{"class":1146},[1086,50976,50977,50980,50982],{"class":1088,"line":3409},[1086,50978,50979],{"class":1436},"    } ",[1086,50981,11084],{"class":1423},[1086,50983,1164],{"class":1436},[1086,50985,50986,50989,50991,50993,50996,50998],{"class":1088,"line":3415},[1086,50987,50988],{"class":1423},"      throw",[1086,50990,23753],{"class":1092},[1086,50992,1398],{"class":1436},[1086,50994,50995],{"class":1096},"\"Failed to load data\"",[1086,50997,1410],{"class":1436},[1086,50999,33466],{"class":1146},[1086,51001,51002],{"class":1088,"line":3421},[1086,51003,1279],{"class":1436},[1086,51005,51006],{"class":1088,"line":3427},[1086,51007,1285],{"class":1436},[1086,51009,51010],{"class":1088,"line":3433},[1086,51011,1291],{"class":1436},[4937,51013],{},[863,51015,51017],{"id":51016},"testing","Testing",[3572,51019],{":items":51020},"[{\"title\":\"1. Open the App\",\"description\":\"Launch Tempus Metronome on your device.\",\"icon\":\"i-lucide-smartphone\"},{\"title\":\"2. Tap Search\",\"description\":\"Open the song search interface.\",\"icon\":\"i-lucide-search\"},{\"title\":\"3. Enter a Song\",\"description\":\"Type a song name or artist to query the GetSongBPM API.\",\"icon\":\"i-lucide-text-cursor-input\"},{\"title\":\"4. BPM Applied\",\"description\":\"The metronome tempo is set automatically to match the song.\",\"icon\":\"i-lucide-check-circle\"}]",[4937,51022],{},[863,51024,51026],{"id":51025},"links","Links",[1045,51028,51030,51034,51037],{"className":51029},[13033,50238,50239,1052],[50241,51031],{"color":50243,"label":51032,"target":50245,"to":51033,"variant":50246},"Tempus on App Store","https://apps.apple.com/pl/app/mtl-tempus/id6755827507",[50241,51035],{"color":50249,"label":50545,"target":50245,"to":51036,"variant":50246},"https://getsongbpm.com/api",[50241,51038],{"color":50249,"label":50646,"target":50245,"to":51039,"variant":50246},"https://getsongbpm.com",[1680,51041,51042],{},"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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .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 .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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":728,"searchDepth":729,"depth":729,"links":51044},[51045,51049,51050,51051,51052,51055,51056],{"id":50551,"depth":729,"text":50552,"children":51046},[51047,51048],{"id":50600,"depth":1112,"text":50601},{"id":50626,"depth":1112,"text":50627},{"id":50637,"depth":729,"text":50638},{"id":50672,"depth":729,"text":50673},{"id":50698,"depth":729,"text":50699},{"id":50740,"depth":729,"text":50741,"children":51053},[51054],{"id":50744,"depth":1112,"text":50745},{"id":51016,"depth":729,"text":51017},{"id":51025,"depth":729,"text":51026},"2025-11-27T00:00:00.000Z","What BPM really means and how we integrated the GetSongBPM API into Tempus Metronome using Flutter to let users look up song tempos instantly.",{"src":51060},"/images/blog/musictechlab_blog_mtl-tempus-getsongbpm.webp",{"enabled":738,"items":51062},[51063,51065,51067],{"text":51064,"icon":2939},"GetSongBPM API returns tempo data in one call with no OAuth, allowing 3,000 requests per hour.",{"text":51066,"icon":3847},"BPM data can be unreliable for live recordings, tempo changes, and double-time patterns.",{"text":51068,"icon":9547},"Tempus Metronome auto-sets the beat from a song search, bridging BPM lookup and practice.",{},{"title":546,"description":51058},[5523,18784],"xoXi9ImXyjWCvQBXMfk3Dwn12IlZedXhrGItym5aJBQ",{"id":51074,"title":281,"authors":51075,"badge":51078,"body":51080,"category":5678,"client":723,"date":51253,"description":5679,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":51254,"keyTakeaways":723,"meta":51256,"navigation":738,"path":282,"seo":51257,"status":723,"stem":283,"tags":51258,"teaser":723,"__hash__":51259},"posts/blog/newsletter/musictech-insights-5-curated-by-maciej-dulski.md",[51076],{"name":50093,"to":50094,"avatar":51077},{"src":50096},{"label":51079,"color":5535},"#5",{"type":725,"value":51081,"toc":51250},[51082,51086,51090,51095,51117,51122,51124,51133,51136,51142,51151,51154,51160,51169,51172,51178,51187,51190,51196,51205,51208,51214,51223,51226,51232,51241,51244],[5539,51083,51085],{"id":51084},"musictech-insights-5-curated-by-maciej-dulski","MusicTech Insights #5 | Curated by Maciej Dulski",[863,51087,51089],{"id":51088},"_7-rounds-in-the-first-10-days-of-november-2025","7 rounds in the first 10 days of November 2025",[842,51091,51092],{},[964,51093,51094],{},"I’m not sure if it’s just a coincidence, but there’s a real wave of optimism in the MusicTech world. In this edition, I’ve gathered everything that happened in MusicTech funding over just ten days. From creation and distribution to data and fan engagement, it’s been a dense and surprisingly upbeat stretch.",[842,51096,51097],{},[964,51098,51099,51100,7826,51105,51110,51111,51116],{},"Adding to that momentum, ",[846,51101,51104],{"href":51102,"rel":51103},"https://www.jukebox.ventures/",[850],"Jukebox Ventures",[846,51106,51109],{"href":51107,"rel":51108},"https://www.musicweek.com/digital/read/jukebox-launches-inaugural-15m-early-stage-venture-fund-to-back-innovation/092890",[850],"announced Fund I",", targeting up to $15M to back startups at the intersection of music, technology, and culture. The same energy was clearly visible during MusicTech Poland Meetup #7: Founder & Investor Talks. ",[846,51112,51115],{"href":51113,"rel":51114},"https://www.linkedin.com/posts/musictech-poland_jakob-wredstr%C3%B8m-how-not-to-do-a-music-startup-activity-7389978544061321216-2ziz",[850],"Recordings from the sessions"," are now available.",[842,51118,51119],{},[964,51120,51121],{},"Seven rounds in ten days. Could November set a new record?",[4937,51123],{},[50359,51125,51127,51132],{"id":51126},"the-dune-app-raises-26m-to-let-fans-invest-directly-in-artists",[846,51128,51131],{"href":51129,"rel":51130},"https://www.theduneapp.com/",[850],"The Dune App"," 🇬🇧 raises $2.6M to let fans invest directly in artists",[842,51134,51135],{},"Manchester-based startup Dune closed a $2.6M Series A to launch a fan-driven artist investment platform. Fans can buy “stakes” in artists that rise or fall based on streaming performance. Artists unlock new revenue streams and data insights, while fans get exclusive perks and a deeper connection. Founded by Paul Bowe, Dune aims to turn fan support into a fair, tradeable music economy.",[842,51137,51138],{},[846,51139,5605],{"href":51140,"rel":51141},"https://www.digitalmusicnews.com/2025/11/04/artist-investment-platform-dune-funding-raise/",[850],[50359,51143,51145,51150],{"id":51144},"muzaic-raises-seed-funding-to-expand-its-ai-driven-soundtrack-platform",[846,51146,51149],{"href":51147,"rel":51148},"https://muzaic.ai/",[850],"Muzaic"," 🇵🇱 raises seed funding to expand its AI-driven soundtrack platform",[842,51152,51153],{},"Polish startup Muzaic, led by Wojtek Hazanowicz, secured a seed round backed by 24Ventures, Digital Ocean Ventures, and Shape VC. The company develops Seamless Soundtracks: AI-generated background music that adapts dynamically to creative projects. With over 250,000 monthly users, Muzaic plans to grow its team, refine the tech, and expand internationally, with a strong focus on responsible AI.",[842,51155,51156],{},[846,51157,5605],{"href":51158,"rel":51159},"https://mamstartup.pl/muzaic-z-finansowaniem-w-rundzie-seed-wsrod-inwestorow-m-in-24ventures-i-digital-ocean-ventures/",[850],[50359,51161,51163,51168],{"id":51162},"deplike-raises-funding-to-scale-its-ai-powered-music-education-platform",[846,51164,51167],{"href":51165,"rel":51166},"https://deplike.com/",[850],"Deplike"," 🇹🇷 raises funding to scale its AI-powered music education platform",[842,51170,51171],{},"Turkish EdTech startup Deplike, led by CEO Ufuk Polat, raised funding from 216 Capital Ventures and other investors. Its app, Chordie AI, combines real-time feedback, 3D tutors, and gamified lessons to help users learn guitar in an engaging way. With more than 250,000 active users and fast revenue growth, the company is preparing for a Series A and international expansion.",[842,51173,51174],{},[846,51175,5605],{"href":51176,"rel":51177},"https://bridgemena.com/19733/",[850],[50359,51179,51181,51186],{"id":51180},"godigital-raises-230m-and-reorganizes-its-music-business",[846,51182,51185],{"href":51183,"rel":51184},"https://godigital.music/",[850],"GoDigital"," 🇺🇸 raises $230M and reorganizes its music business",[842,51188,51189],{},"GoDigital secured $230M in funding led by Bank of America, with participation from Mitsubishi UFJ Financial Group, East West Bank, and others. The company reorganized into three units: Music, Networks, and Brands. Cinq Music now sits within GoDigital Music and will undergo a rebrand while continuing to acquire and manage catalogs across genres including Reggaetón, Afrobeats, K-pop, and country.",[842,51191,51192],{},[846,51193,5605],{"href":51194,"rel":51195},"https://www.musicbusinessworldwide.com/cinq-music-parent-company-rebrands-raises-230m-in-fresh-funding-via-new-godigital-music-division1/",[850],[50359,51197,51199,51204],{"id":51198},"music-tomorrow-raises-1m-to-bring-transparency-to-music-ai",[846,51200,51203],{"href":51201,"rel":51202},"https://music-tomorrow.com/",[850],"Music Tomorrow"," 🇫🇷 raises €1M to bring transparency to Music AI",[842,51206,51207],{},"This seed round was led by Inovexus, SideAngels, APOK Invest, Bolboreta Innova Group, Bpifrance, and IFCIC. Led by Benoit Menet, Music Tomorrow analyzes how DSP algorithms perceive artists and catalogs. The platform helps labels and artists optimize marketing budgets, increase streams, and improve audience targeting, with strong, measurable results across 150+ artists and 30+ labels.",[842,51209,51210],{},[846,51211,5605],{"href":51212,"rel":51213},"https://www.linkedin.com/posts/music-tomorrow_were-thrilled-to-announce-that-music-activity-7391139583826169856-qvv-/",[850],[50359,51215,51217,51222],{"id":51216},"ticketoo-wins-200k-for-fan-to-fan-ticketing-platform",[846,51218,51221],{"href":51219,"rel":51220},"https://ticketoo.it/en",[850],"Ticketoo"," 🇮🇹 wins €200K for fan-to-fan ticketing platform",[842,51224,51225],{},"Italian startup Ticketoo, led by Simone Serani, won a €200K equity investment at the Get it! 4 Music Demo Day. The platform enables fans to safely and transparently buy and sell event tickets. With over 500,000 registered users, Ticketoo focuses on democratizing access to live shows while scaling its secondary ticketing marketplace.",[842,51227,51228],{},[846,51229,5605],{"href":51230,"rel":51231},"https://cariplofactory.it/news/get-it-4-music-premia-ticketoo-e-milano-songwriting-academy/",[850],[50359,51233,51235,51240],{"id":51234},"freya-raises-35m-for-secure-voice-ai-in-enterprise-communication",[846,51236,51239],{"href":51237,"rel":51238},"https://freyavoice.ai/",[850],"Freya"," 🇨🇿 raises $3.5M for secure voice AI in enterprise communication",[842,51242,51243],{},"Freya builds secure, human-like voice AI agents for inbound and outbound business calls in banking, fintech, and insurance. The platform works both in the cloud and on-premise, with a strong focus on compliance and security. While enterprise-focused, the underlying voice and audio AI tech closely overlaps with tools used in music and creative audio workflows.",[842,51245,51246],{},[846,51247,5605],{"href":51248,"rel":51249},"https://en.ain.ua/2025/11/05/freya-raises-35m/",[850],{"title":728,"searchDepth":729,"depth":729,"links":51251},[51252],{"id":51088,"depth":729,"text":51089},"2025-11-10T00:00:00.000Z",{"src":51255},"/images/blog/musictechlab_blog_musictech-insights-5-maciej-dulski.webp",{},{"title":281,"description":5679},[5678,5523],"QyuyZj6EOlOw-92rqknSwnfieE-vgvvlnLsUNum3-m0",{"id":51261,"title":277,"authors":51262,"badge":51268,"body":51270,"category":5678,"client":723,"date":51377,"description":5679,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":51378,"keyTakeaways":723,"meta":51380,"navigation":738,"path":278,"seo":51381,"status":723,"stem":279,"tags":51382,"teaser":723,"__hash__":51383},"posts/blog/newsletter/musictech-insights-4-curated-by-amanda-schupf.md",[51263],{"name":51264,"to":51265,"avatar":51266},"Amanda Schupf","https://www.linkedin.com/in/amandaschupf/",{"src":51267},"/images/partners/musictechlab_partners_amanda-schupf.webp",{"label":51269,"color":5535},"#4",{"type":725,"value":51271,"toc":51374},[51272,51276,51280,51289,51294,51296,51300,51303,51309,51313,51316,51322,51326,51329,51335,51339,51342,51348,51352,51355,51361,51365,51368],[5539,51273,51275],{"id":51274},"musictech-insights-4-curated-by-amanda-schupf","MusicTech Insights #4 | Curated by Amanda Schupf",[863,51277,51279],{"id":51278},"the-music-metadata-conundrum-can-there-ever-really-be-one-source-of-truth","The Music Metadata Conundrum: Can there ever really be one source of truth?",[842,51281,51282],{},[964,51283,51284,51285,51288],{},"Hi, I’m ",[846,51286,51264],{"href":51265,"rel":51287},[850],". I’ve spent years watching money disappear into black holes because two databases can’t agree on who owns what. Metadata isn’t sexy, but it’s literally the skeleton the entire modern music industry hangs on. Without clean, connected metadata there is no accurate royalty payment, no proper sync licensing, no trustworthy AI training data, and no real discovery. The dream of one single, global, always-correct “source of truth” has been chased for decades.",[842,51290,51291],{},[964,51292,51293],{},"Spoiler: as long as humans are involved (and they always will be), perfection is impossible. But we’re finally seeing real momentum toward something good enough.",[4937,51295],{},[50359,51297,51299],{"id":51298},"jaxstas-early-leap-toward-connecting-musics-metadata-dots","Jaxsta's early leap toward connecting music’s metadata dots",[842,51301,51302],{},"Way back in 2022 – before “metadata infrastructure” was cool – Jaxsta launched one of the first serious attempts to link musical works to recordings across publishing and labels. A quiet but important step that’s still paying dividends for rights holders and fans alike.",[842,51304,51305],{},[846,51306,5605],{"href":51307,"rel":51308},"https://www.musicbusinessworldwide.com/jaxsta-introduces-new-metadata-matching-tool-for-rightsholders/",[850],[50359,51310,51312],{"id":51311},"musicinfra-raises-5m-to-tackle-metadata-complexity","MusicInfra raises $5M to tackle metadata complexity",[842,51314,51315],{},"Fresh funding and the addition of industry veteran Jules Parker show that MusicInfra is going all-in on solving the hardest parts of metadata at scale. They understand it’s not just a tech problem – it’s a people and collaboration problem.",[842,51317,51318],{},[846,51319,5605],{"href":51320,"rel":51321},"https://newindustryfocus.com/articles/music-rights-company-musicinfra-raises-5m-in-funding-round",[850],[50359,51323,51325],{"id":51324},"sesac-and-gmr-join-songview-to-unify-copyright-data","SESAC and GMR join Songview to unify copyright data",[842,51327,51328],{},"The fact that SESAC and GMR are now sitting at the same table as ASCAP and BMI inside Songview is one of the biggest pro-collaboration moves we’ve seen from the PRO world in years. Fragmentation is slowly losing.",[842,51330,51331],{},[846,51332,5605],{"href":51333,"rel":51334},"https://www.billboard.com/pro/sesac-gmr-songview-music-copyright-data-platform-ascap-bmi/",[850],[50359,51336,51338],{"id":51337},"mogul-shows-what-broken-metadata-really-costs","Mogul shows what broken metadata really costs",[842,51340,51341],{},"In less than a year Mogul has already uncovered millions in missing royalties for artists and managers. Nothing illustrates the real-world price of messy metadata better than cold, hard cash that never reached its owner.",[842,51343,51344],{},[846,51345,5605],{"href":51346,"rel":51347},"https://www.musicbusinessworldwide.com/music-startup-mogul-surpasses-100-million-in-tracked-royalties-in-three-months-since-launch/",[850],[50359,51349,51351],{"id":51350},"watermusic-put-the-metadata-problem-in-context","Water&Music put the metadata problem in context",[842,51353,51354],{},"Last year’s deep-dive report from Water&Music remains the best map of where capital is flowing into metadata, rights management and music-tech infrastructure. It’s required reading for anyone who thinks this is “just a data issue”.",[842,51356,51357],{},[846,51358,5605],{"href":51359,"rel":51360},"https://www.waterandmusic.com/music-tech-money-flows-2024/",[850],[50359,51362,51364],{"id":51363},"exploring-the-idea-of-a-shared-open-layer-for-music-metadata","Exploring the Idea of a Shared, Open Layer for Music Metadata",[842,51366,51367],{},"An academic paper that dares to ask: what if we borrowed ideas from libraries and publishing and built one open, authoritative, shared metadata layer for the entire industry? The concept is still waiting for its builder.",[842,51369,51370],{},[846,51371,5605],{"href":51372,"rel":51373},"https://arxiv.org/abs/1911.08278",[850],{"title":728,"searchDepth":729,"depth":729,"links":51375},[51376],{"id":51278,"depth":729,"text":51279},"2025-10-15T00:00:00.000Z",{"src":51379},"/images/blog/musictechlab_blog_musictech-insights-4-amanda-schupf.webp",{},{"title":277,"description":5679},[5678,731],"ABgD3VH2jSdB0sEW2WNcIyNYNrqgnmme5_0Lsn-FQLA",{"id":51385,"title":273,"authors":51386,"badge":51392,"body":51394,"category":5678,"client":723,"date":51492,"description":5679,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":51493,"keyTakeaways":723,"meta":51495,"navigation":738,"path":274,"seo":51496,"status":723,"stem":275,"tags":51497,"teaser":723,"__hash__":51498},"posts/blog/newsletter/musictech-insights-3-curated-by-drew-thurlow.md",[51387],{"name":51388,"to":51389,"avatar":51390},"Drew Thurlow","https://www.linkedin.com/in/drewthurlow/",{"src":51391},"https://www.musictechlab.io/_ipx/_/images/partners/Drew.webp",{"label":51393,"color":5535},"#3",{"type":725,"value":51395,"toc":51489},[51396,51400,51403,51406,51409,51411,51415,51418,51424,51428,51431,51437,51441,51444,51450,51454,51457,51463,51467,51470,51476,51480,51483],[5539,51397,51399],{"id":51398},"musictech-insights-3-curated-by-drew-thurlow-musictech-lab","MusicTech Insights #3 | Curated by Drew Thurlow & MusicTech Lab",[863,51401,273],{"id":51402},"ai-in-music-hype-hope-and-a-human-touch",[842,51404,51405],{},"Hi, my name is Drew. I’ve spent over two decades at the intersection of music and technology, working with record labels, streaming platforms, and startups aiming to redefine the future of sound. Each revolution promised to transform everything instantly. Some did. Others just left us with new buzzwords.",[842,51407,51408],{},"In partnership with MusicTech Lab, I’ve put together a selection of AI-in-music stories that I believe are timely, useful, and relevant for anyone navigating this space. From new research and business models to the ongoing debates about ownership and creativity, the goal is simple: to keep the human voice at the center while finding harmony with technology.",[4937,51410],{},[50359,51412,51414],{"id":51413},"darren-hemmings-lets-cut-the-ai-hysteria","Darren Hemmings: Let’s cut the AI hysteria",[842,51416,51417],{},"Founder of Motive Unknown argues that we need less hysteria around AI. His latest Network Notes post acknowledges AI’s power and reach but reminds us that its limits matter too. In a sea of hype, Darren’s take is a calm reality check.",[842,51419,51420],{},[846,51421,5605],{"href":51422,"rel":51423},"https://networknotes.motiveunknown.com/p/the-irritating-totality-of-talk-around",[850],[50359,51425,51427],{"id":51426},"deezer-warns-1-in-3-uploads-is-now-ai-generated","Deezer warns: 1 in 3 uploads is now AI-generated",[842,51429,51430],{},"Nearly a third of new tracks uploaded to DSPs are fully AI-generated. Still, they barely register in royalty payouts and most listeners ignore them. As the noise grows, it’s worth noting: low-quality filler has always existed, and humans are no strangers to producing it.",[842,51432,51433],{},[846,51434,5605],{"href":51435,"rel":51436},"https://www.musicbusinessworldwide.com/nearly-a-third-of-all-tracks-uploaded-to-deezer-are-now-fully-ai-generated-says-platform/",[850],[50359,51438,51440],{"id":51439},"the-rise-of-artist-inc-artists-as-full-blown-businesses","The rise of “Artist Inc.”: artists as full-blown businesses",[842,51442,51443],{},"Nate Fisher explores a shift in how artists partner with capital. Instead of transactional deals, firms like Firebird invest holistically while leaving artists in control. It’s a model that reframes the artist not as a roster entry but as a standalone business.",[842,51445,51446],{},[846,51447,5605],{"href":51448,"rel":51449},"https://alderbrook.substack.com/p/exploring-the-artist-inc-funding",[850],[50359,51451,51453],{"id":51452},"new-study-reveals-how-to-detect-ai-vocals-in-music","New study reveals how to detect AI vocals in music",[842,51455,51456],{},"A new academic paper co-authored by Gabriel Levine, Drew Thurlow, Sarah Ita Levitan, and Jon Arfa tackles the challenge of spotting AI-generated vocals. With voice being one of the most human and fragile elements of music, the study highlights how detection tools are crucial for trust and authenticity.",[842,51458,51459],{},[846,51460,5605],{"href":51461,"rel":51462},"https://zenodo.org/records/16955639",[850],[50359,51464,51466],{"id":51465},"mike-pelczynski-the-three-rules-every-music-business-needs","Mike Pelczynski: The three rules every music business needs",[842,51468,51469],{},"In his Forms + Shapes Substack, Mike Pelczynski strips the industry down to essentials: ownership, boundaries, and freedom. Drawing on years of experience shaping royalty models, he argues that clarity on these basics is the best way to avoid being boxed in by contracts or platforms.",[842,51471,51472],{},[846,51473,5605],{"href":51474,"rel":51475},"https://formsandshapes.substack.com/p/tenets-of-the-music-business",[850],[50359,51477,51479],{"id":51478},"dave-godowsky-why-true-partnerships-start-with-the-artist","Dave Godowsky: Why true partnerships start with the artist",[842,51481,51482],{},"On a recent Music Tectonics podcast, Dave Godowsky explains why true partnerships keep creativity at the core. With years of experience bridging artists and business, his perspective is a reminder that without artists, there is no industry. Something too often lost in corporate talk.",[842,51484,51485],{},[846,51486,5605],{"href":51487,"rel":51488},"https://www.musictectonics.com/post/what-makes-artist-partnerships-meaningful-with-dave-godowsky",[850],{"title":728,"searchDepth":729,"depth":729,"links":51490},[51491],{"id":51402,"depth":729,"text":273},"2025-09-18T00:00:00.000Z",{"src":51494},"/images/blog/musictechlab_blog_musictech-insights-3-drew-thurlow.webp",{},{"title":273,"description":5679},[5678,14100],"8A5hJYkTTdRQUBqV0WyLfXeOGQtV689hSU8NwE1Y4M0",{"id":51500,"title":269,"authors":51501,"badge":51504,"body":51506,"category":5678,"client":723,"date":51633,"description":5679,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":51634,"keyTakeaways":723,"meta":51636,"navigation":738,"path":270,"seo":51637,"status":723,"stem":271,"tags":51638,"teaser":723,"__hash__":51639},"posts/blog/newsletter/musictech-insights-2-curated-by-maciej-dulski.md",[51502],{"name":50093,"to":50094,"avatar":51503},{"src":50096},{"label":51505,"color":5535},"#2",{"type":725,"value":51507,"toc":51630},[51508,51512,51515,51518,51521,51524,51526,51530,51533,51539,51543,51546,51552,51556,51559,51565,51569,51572,51578,51582,51585,51591,51595,51598,51604,51608,51611,51617,51621,51624],[5539,51509,51511],{"id":51510},"musictech-insights-2-curated-by-maciej-dulski","MusicTech Insights #2 | Curated by Maciej Dulski",[863,51513,269],{"id":51514},"feeling-the-musictech-momentum",[842,51516,51517],{},"I really feel change happening in MusicTech right now. This issue is packed with insights for founders: from current trends and VCs’ views on startups to why many struggle with funding. It’s all about getting a clear picture of where the industry is headed.",[842,51519,51520],{},"Joining DDEX has been a big personal goal for years. I believe that knowing music data standards and sharing that know-how is key to making the whole industry run smoothly and fairly. If you want to chat with someone who’s just as passionate (or maybe a little nerdy) about this, we’re your people.",[842,51522,51523],{},"Lastly, there are some surprises planned for future newsletters, so keep an eye out and stay close.",[4937,51525],{},[50359,51527,51529],{"id":51528},"polands-most-played-artist-on-spotify-is-a-21-year-old-no-one-has-heard-of","Poland’s most-played artist on Spotify is a 21-year-old no one has heard of",[842,51531,51532],{},"He makes over $2.5M from Spotify, has 8.3M monthly listeners, and you won’t find him on Polish charts. Ogryzek is a faceless producer behind viral TikToks and Shorts, but his name never appears in media. His tracks reach millions daily, yet he stays nearly invisible.",[842,51534,51535],{},[846,51536,5605],{"href":51537,"rel":51538},"https://open.spotify.com/artist/1Sdc6ySbIvzO0X9vbyHzWm",[850],[50359,51540,51542],{"id":51541},"bill-burr-vs-ai-band-these-nerds-took-over-music","Bill Burr vs AI band: “These nerds took over music”",[842,51544,51545],{},"An AI-generated band called Velvet Sundown hit 470,000 monthly listeners on Spotify before anyone realized it wasn’t real. The songs were made with Suno, the images were AI-generated, and even the supposed spokesperson turned out to be fake. Bill Burr called it “unbelievable” and slammed the industry for letting bots replace real musicians.",[842,51547,51548],{},[846,51549,5605],{"href":51550,"rel":51551},"https://musictech.com/news/music/bill-burr-slams-the-velvet-sundown/",[850],[50359,51553,51555],{"id":51554},"inside-jakob-wredstrøms-unique-research-why-music-tech-startups-starve-while-billions-flow","Inside Jakob Wredstrøm’s unique research: why music tech startups starve while billions flow",[842,51557,51558],{},"The music industry moves billions, but most early-stage music tech founders can’t access that capital. After two years of research, interviews, and working directly with startups, Jakob Wredstrøm uncovered six key barriers blocking funding. His new framework helps founders communicate better, clear confusion, and improve their chances to raise money.",[842,51560,51561],{},[846,51562,5605],{"href":51563,"rel":51564},"https://www.linkedin.com/pulse/why-music-tech-startups-starve-while-billions-flow-top-wredstr%C3%B8m-4izhf/",[850],[50359,51566,51568],{"id":51567},"gartner-hype-cycle-mapped-for-musictech-trends-in-2025","Gartner Hype Cycle mapped for MusicTech trends in 2025",[842,51570,51571],{},"Mmusicben (Benjamin James) mapped 20 music tech trends using data and expert insight. Some are at their peak, others are falling out of favor, and a few are just starting up. This overview helps artists, startups, and industry pros know where to focus their energy and spot real opportunities.",[842,51573,51574],{},[846,51575,5605],{"href":51576,"rel":51577},"https://ziggyziggymusic.substack.com/p/which-music-tech-is-too-over-hyped",[850],[50359,51579,51581],{"id":51580},"musictech-lab-joins-ddex-as-full-member","MusicTech Lab joins DDEX as full member",[842,51583,51584],{},"We’ll help startups and teams understand how to handle music rights, royalties, and reporting by providing clear guidance and practical tools to simplify complex data workflows. If you’re working with music metadata and want to learn how to use DDEX standards effectively, we can support you.",[842,51586,51587],{},[846,51588,5605],{"href":51589,"rel":51590},"https://www.linkedin.com/feed/update/urn:li:activity:7353834160530034688",[850],[50359,51592,51594],{"id":51593},"apple-brings-humans-back-to-music-curation-and-classical-music-thanks-them","Apple brings humans back to music curation and classical music thanks them",[842,51596,51597],{},"Most streaming playlists bury classical music under endless repeats of the same safe hits. Apple Music Classical’s hidden team of editors picks real gems and deep cuts. They decide what millions hear, quietly pushing the genre back to life and breaking free from mind-numbing algorithms.",[842,51599,51600],{},[846,51601,5605],{"href":51602,"rel":51603},"https://www.telegraph.co.uk/music/classical-music/apple-music-classical-music/",[850],[50359,51605,51607],{"id":51606},"according-to-goldman-sachs-emerging-markets-superfans-and-live-shows-drive-music-growth","According to Goldman Sachs emerging markets, superfans and live shows drive music growth",[842,51609,51610],{},"The July 2025 Music in the Air report shows the music industry is still rocking. Streaming keeps growing, driven by emerging markets, prices are climbing, superfans are spending more cash, and live concerts are back in full swing. AI hasn’t taken over the party yet, but it’s coming.",[842,51612,51613],{},[846,51614,5605],{"href":51615,"rel":51616},"https://www.musicbusinessworldwide.com/emerging-markets-superfans-and-price-rises-7-takeaways-from-goldman-sachs-new-music-in-the-air-report/",[850],[50359,51618,51620],{"id":51619},"outpost-partners-exclusive-report-tracks-600-music-tech-startups-and-funding-trends","Outpost Partners’ exclusive report tracks 600+ music tech startups and funding trends",[842,51622,51623],{},"The report reveals GenAI startups now make up nearly 24% of deals, with Europe and North America dominating 98% of investments. Early-stage raises average $2M, but many face a funding crunch at Series A. Meanwhile, crypto-based fan engagement startups are disappearing. Valuations are rising again, signaling renewed investor confidence.",[842,51625,51626],{},[846,51627,5605],{"href":51628,"rel":51629},"https://outpost.partners/music_tech_report_2025/",[850],{"title":728,"searchDepth":729,"depth":729,"links":51631},[51632],{"id":51514,"depth":729,"text":269},"2025-09-17T00:00:00.000Z",{"src":51635},"/images/blog/musictechlab_blog_musictech-insights_2_maciej_dulski.webp",{},{"title":269,"description":5679},[5678,5523],"4077z18FpRuLu6ZyRorKiTFzIbiWdhC246zll9OCgKg",{"id":51641,"title":265,"authors":51642,"badge":51645,"body":51647,"category":5678,"client":723,"date":51775,"description":5679,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":51776,"keyTakeaways":723,"meta":51778,"navigation":738,"path":266,"seo":51779,"status":723,"stem":267,"tags":51780,"teaser":723,"__hash__":51781},"posts/blog/newsletter/musictech-insights-1-curated-by-maciej-dulski.md",[51643],{"name":50093,"to":50094,"avatar":51644},{"src":50096},{"label":51646,"color":5535},"#1",{"type":725,"value":51648,"toc":51772},[51649,51653,51657,51660,51663,51666,51668,51672,51675,51681,51685,51688,51694,51698,51701,51707,51711,51714,51720,51724,51727,51733,51737,51740,51746,51750,51753,51759,51763,51766],[5539,51650,51652],{"id":51651},"musictech-insights-1-curated-by-maciej-dulski","MusicTech Insights #1 | Curated by Maciej Dulski",[863,51654,51656],{"id":51655},"the-ultimate-musictech-newsletter-for-anyone-who-believes-musictech-can-groove","The Ultimate MusicTech Newsletter for Anyone Who Believes MusicTech Can Groove",[842,51658,51659],{},"We know, another newsletter. But here it’s all substance, no fluff. Our goal is to deliver carefully picked news and sharp insights from the music tech world that you can actually drop in meetings or casually mention at a barbecue.",[842,51661,51662],{},"Every update will share handy tools, smart ideas and real inspiration to help you grow your projects or even kick off something fresh. MusicTech Insights is backed by our trusted partners from all corners of the music industry. Together we dig deep to deliver handpicked insider content, the kind of stuff usually reserved for the inner circle.",[842,51664,51665],{},"We’re music-loving tech geeks who take our work seriously but always keep things fun and light. Think of this as your backstage pass to the latest trends and tools packed with info you can actually use every day.",[4937,51667],{},[50359,51669,51671],{"id":51670},"googles-magenta-rt-creates-music-faster-than-it-plays-for-real-time-ai-jams","Google’s Magenta RT creates music faster than it plays for real-time AI jams",[842,51673,51674],{},"This new AI model does more than generate music. It plays live with musicians and lets them steer genres and sounds on the fly. Unlike older tools it works faster than playback which enables true real-time collaboration. This marks a shift from passive generation to active performance opening fresh creative possibilities in music.",[842,51676,51677],{},[846,51678,5605],{"href":51679,"rel":51680},"https://magenta.withgoogle.com/magenta-realtime",[850],[50359,51682,51684],{"id":51683},"sesh-raises-7m-to-let-artists-message-fans-directly-no-app-needed","Sesh raises $7M to let artists message fans directly, no app needed",[842,51686,51687],{},"This platform lets artists skip social media and reach fans through wallet-based Member Cards. Push notifications hit lock screens with news on drops, events, or releases. By owning the data and building real communities, creators stop relying on rented platforms.",[842,51689,51690],{},[846,51691,5605],{"href":51692,"rel":51693},"https://gamesbeat.com/sesh-raises-7m-for-new-fan-communities-and-launches-member-card/",[850],[50359,51695,51697],{"id":51696},"independent-venues-create-153-billion-and-900000-jobs","Independent venues create $153 billion and 900,000 jobs",[842,51699,51700],{},"NIVA’s study reveals indie venues fuel $86 billion in US GDP and $153 billion total impact through fan spending and tourism. Nearly a million jobs depend on this scene. For tech founders, venue owners, and artists, these numbers show where innovation and smarter tools can boost growth and fix profitability in this vital sector.",[842,51702,51703],{},[846,51704,5605],{"href":51705,"rel":51706},"https://www.jambase.com/article/niva-state-of-live-report-findings",[850],[50359,51708,51710],{"id":51709},"max-martin-backs-maia-universe-as-it-tackles-the-tricky-sync-licensing-bottleneck","Max Martin backs MAIA Universe as it tackles the tricky sync licensing bottleneck",[842,51712,51713],{},"How do you simplify sync licensing to help artists and publishers get paid faster? MAIA Universe aims to fix this long-standing pain point. With 1.3 million dollars raised including Max Martin as investor and big licensing deals with Sony and Warner the startup is set to speed up sync deals and unlock new revenue for music creators.",[842,51715,51716],{},[846,51717,5605],{"href":51718,"rel":51719},"https://www.musicbusinessworldwide.com/max-martin-invests-in-maia-universe-as-music-tech-startup-closes-1-3m-round-strikes-new-licensing-deals1/",[850],[50359,51721,51723],{"id":51722},"deezer-fights-streaming-fraud-as-ai-generated-tracks-flood-the-platform","Deezer fights streaming fraud as AI-generated tracks flood the platform",[842,51725,51726],{},"The share of AI-made songs on Deezer jumped from 10 to 18 percent in just months. Many streams come from bots trying to collect royalties fraudulently. Deezer uses AI tools to tag these songs and fight fake streams. This rapid growth of fake plays raises big questions for anyone building tech in music about trust and protecting artists.",[842,51728,51729],{},[846,51730,5605],{"href":51731,"rel":51732},"https://newsroom-deezer.com/2025/06/deezer-launches-worlds-first-ai-tagging-system-for-music-streaming/",[850],[50359,51734,51736],{"id":51735},"a-startups-sex-toy-that-makes-you-feel-music-and-is-smashing-kickstarter","A startup’s sex toy that makes you feel music and is smashing Kickstarter",[842,51738,51739],{},"Groove Thing raised nearly $262,000 way over its $10,000 goal with three weeks still to go. Their product combines a music speaker and vibrator to turn your favorite tracks into full-body pleasure. Feel the bass riffs and beats where it really counts. Perfect for solo fun or syncing up with a partner. Music just got a whole lot kinkier.",[842,51741,51742],{},[846,51743,5605],{"href":51744,"rel":51745},"https://www.kickstarter.com/projects/groove-thing/the-worlds-first-internal-music-player",[850],[50359,51747,51749],{"id":51748},"musictech-lab-partners-with-base-for-music-and-soundcharts","MusicTech Lab partners with Base for Music and Soundcharts",[842,51751,51752],{},"We teamed up with Base for Music and Soundcharts to bring you great offers. Use code MUSICTECH15 for 15% off your first Base for Music campaign. With Soundcharts, get 30% off lifetime access using MUSICTECHLAB30 and benefit from free consultations on integrating their real-time music data platform.",[842,51754,51755],{},[846,51756,5605],{"href":51757,"rel":51758},"https://www.musictechlab.io/partners/soundcharts",[850],[50359,51760,51762],{"id":51761},"vinyl-group-gets-15m-from-songtradr-in-move-that-hints-at-value-beyond-digital","Vinyl Group gets $1.5M from Songtradr in move that hints at value beyond digital",[842,51764,51765],{},"Songtradr, usually focused on streaming and data, backs a physical format play through Vinyl.com. For founders, this might be a signal to look beyond the obvious. Vinyl is not just nostalgia. It could be part of a more diverse music future.",[842,51767,51768],{},[846,51769,5605],{"href":51770,"rel":51771},"https://au.variety.com/2025/more/news/vinyl-group-songtradr-1-5-million-revolving-credit-line-24345/",[850],{"title":728,"searchDepth":729,"depth":729,"links":51773},[51774],{"id":51655,"depth":729,"text":51656},"2025-07-11T00:00:00.000Z",{"src":51777},"/images/blog/musictechlab_blog_musictech-insights_1_maciej_dulski.webp",{},{"title":265,"description":5679},[5678,5523],"vYvcdjUPApCd3UU25QV1j52kqobtKTIhZ5_eDHfA28c",{"id":51783,"title":172,"authors":51784,"badge":51787,"body":51788,"category":731,"client":723,"date":51872,"description":51873,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":51874,"keyTakeaways":51876,"meta":51884,"navigation":738,"path":173,"seo":51885,"status":723,"stem":174,"tags":51886,"teaser":723,"__hash__":51887},"posts/blog/music-data/musictech-resources-curated-insights-for-the-musictech-builders.md",[51785],{"name":50093,"to":50094,"avatar":51786},{"src":50096},{"label":50098,"color":50099},{"type":725,"value":51789,"toc":51866},[51790,51793,51796,51807,51811,51814,51834,51837,51841,51844,51847,51850,51853,51857,51860],[842,51791,51792],{},"At MusicTech Lab, we often found ourselves wishing for a single, reliable place to track what’s happening across the MusicTech landscape - from investments and acquisitions to the people, tools, and ideas shaping the future of music and technology.",[842,51794,51795],{},"So we built it.",[842,51797,51798,51799,51806],{},"We’re proud to introduce ",[846,51800,51803],{"href":51801,"rel":51802},"https://www.musictechlab.io/resources",[850],[996,51804,51805],{},"MusicTech Resources"," - a curated platform designed to support founders, professionals, and teams working at the intersection of music, audio, and innovation.",[1074,51808,51810],{"id":51809},"what-youll-find","What you’ll find",[842,51812,51813],{},"MusicTech Resources brings together the most relevant and up-to-date information from across the sector. If you're building a startup in music, audio, or entertainment, the platform includes:",[958,51815,51816,51819,51822,51825,51828,51831],{},[961,51817,51818],{},"A dataset of ~500 investor/startup deals",[961,51820,51821],{},"A timeline of recent acquisitions in the space",[961,51823,51824],{},"A growing database of MusicTech startups",[961,51826,51827],{},"A global calendar of upcoming events and webinars",[961,51829,51830],{},"A curated list of open-source projects",[961,51832,51833],{},"A Partner section featuring experts we trust in areas like fundraising, metadata, and market strategy",[842,51835,51836],{},"Each collection is designed to be practical, relevant, and easy to navigate - whether you're raising a round, looking for collaborators, or staying informed about emerging trends.",[1074,51838,51840],{"id":51839},"why-subscribe","Why subscribe?",[842,51842,51843],{},"To access the full platform, you’ll need to sign up. It’s free and gives you full access to all resources, along with a newsletter focused on real updates, fresh insights, and curated content that actually matters to MusicTech professionals.",[1074,51845,51846],{"id":16446},"What’s next?",[842,51848,51849],{},"We’re already working on expanding the platform with new Collections. Our long-term goal is to build the most useful, up-to-date resource for anyone serious about MusicTech - whether you're a founder, investor, researcher, or part of a larger organization.",[842,51851,51852],{},"We know there are other ways to map this space. This is ours - and we’re excited to keep improving it.",[1074,51854,51856],{"id":51855},"built-with-care","Built with care",[842,51858,51859],{},"This project was made possible by the dedicated team at MusicTech Lab, with contributions from Mariusz Smenżyk, Klaudia Matysiak, Miłosz Jasica, Katarzyna Czerniak, and many others. We’re proud of what we’ve created, and we hope it helps move the industry forward.",[1045,51861,51863],{"className":51862},[13033,50238,50239,1052],[50241,51864],{"color":50243,"label":51865,"target":50245,"to":51801,"variant":50246},"Explore MusicTech Resources",{"title":728,"searchDepth":729,"depth":729,"links":51867},[51868,51869,51870,51871],{"id":51809,"depth":1112,"text":51810},{"id":51839,"depth":1112,"text":51840},{"id":16446,"depth":1112,"text":51846},{"id":51855,"depth":1112,"text":51856},"2025-06-03T00:00:00.000Z","A curated platform for MusicTech founders and teams. Track investor deals, explore open-source tools, and find partners and events in the music-tech space.",{"src":51875},"/images/blog/musictechlab_blog_musictech-resources-curated-insights-for-the-musictech-builders.webp",{"enabled":738,"items":51877},[51878,51880,51882],{"text":51879,"icon":3920},"MusicTech Resources includes ~500 investor/startup deals and a global events calendar.",{"text":51881,"icon":2895},"Free sign-up gives full access to startup databases, open source tools, and partner listings.",{"text":51883,"icon":4845},"Curated by MusicTech Lab for founders, investors, and professionals in the music tech space.",{},{"title":172,"description":51873},[5523,731],"SWRZKGtiEglEnvpES_KLUH7zTi0Yk5PDmhvq_oTRfK0",{"id":51889,"title":176,"authors":51890,"badge":51893,"body":51894,"category":731,"client":723,"date":51969,"description":51970,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":51971,"keyTakeaways":51973,"meta":51981,"navigation":738,"path":177,"seo":51982,"status":723,"stem":178,"tags":51983,"teaser":723,"__hash__":51984},"posts/blog/music-data/polands-creative-tech-sector-is-on-the-rise-and-musictech-is-part-of-it.md",[51891],{"name":834,"to":720,"avatar":51892},{"src":722},{"label":50098,"color":50099},{"type":725,"value":51895,"toc":51965},[51896,51899,51905,51916,51920,51930,51933,51944,51948,51951,51962],[842,51897,51898],{},"A new report just dropped that every builder, founder, and policymaker working at the intersection of culture and technology should take a look at.",[842,51900,51901,51904],{},[996,51902,51903],{},"“Poland Creative Tech Startups Report 2025”"," is the first comprehensive study of Poland’s creative tech scene - covering over 400 startups, investors, and public institutions. It includes key data, expert commentary, and practical recommendations for developing this fast-moving space.",[1045,51906,51908,51912],{"className":51907},[13033,50238,50239,1052],[50241,51909],{"color":50243,"label":51910,"target":50245,"to":51911,"variant":50246},"Polish version","https://lnkd.in/e2qeNwTr",[50241,51913],{"color":50249,"label":51914,"target":50245,"to":51915,"variant":50246},"English version","https://lnkd.in/ed-SWFJj",[863,51917,51919],{"id":51918},"musictech-labs-take","MusicTech Lab’s Take",[842,51921,51922,51923,51925,51926,51929],{},"We’re excited that our R&D Partner, ",[996,51924,50093],{},", was invited to share his perspective in the report, especially as co-founder of ",[996,51927,51928],{},"MusicTech Poland",", a growing community that supports music + tech collaboration in the region.",[842,51931,51932],{},"In the featured case study, Maciej talks about:",[958,51934,51935,51938,51941],{},[961,51936,51937],{},"the current innovation gap in Poland’s music sector,",[961,51939,51940],{},"the role of MusicTech Poland in building a local ecosystem,",[961,51942,51943],{},"and how we at MusicTech Lab support founders working on tools, platforms, and AI-driven products for the music industry.",[863,51945,51947],{"id":51946},"why-this-report-matters","Why This Report Matters",[842,51949,51950],{},"This report shows that Poland has everything needed to lead creative innovation in Central Europe - talent, products, and ideas that can compete globally. But to get there, we need:",[958,51952,51953,51956,51959],{},[961,51954,51955],{},"better support for founders and builders,",[961,51957,51958],{},"more partnerships between culture and tech,",[961,51960,51961],{},"and brave investments in the tools that shape how music and art are created today.",[842,51963,51964],{},"We’re proud to contribute to this momentum. If you’re a startup, investor, or institution working in musictech, let’s talk.",{"title":728,"searchDepth":729,"depth":729,"links":51966},[51967,51968],{"id":51918,"depth":729,"text":51919},{"id":51946,"depth":729,"text":51947},"2025-06-02T00:00:00.000Z","Poland’s first Creative Tech report is here and MusicTech Lab’s Maciej Dulski shares insights on how music innovation in Poland is gaining momentum.",{"src":51972},"/images/blog/musictechlab_blog_polands-creative-tech-sector-is-on-the-rise-and-musictech-is-part-of-it.webp",{"enabled":738,"items":51974},[51975,51977,51979],{"text":51976,"icon":3844},"Poland's first Creative Tech report covers 400+ startups, investors, and public institutions.",{"text":51978,"icon":50270},"Maciej Dulski contributed a case study on the innovation gap in Poland's music sector.",{"text":51980,"icon":4845},"MusicTech Poland community supports music and tech collaboration across the region.",{},{"title":176,"description":51970},[5523,731],"z1sKzcQtqgbYZYKDz2XGi6YFXaUsZPGk6mAypUwapQU",{"id":51986,"title":62,"authors":723,"badge":51987,"body":51988,"category":4990,"client":52255,"date":52258,"description":52259,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":52260,"keyTakeaways":52262,"meta":52273,"navigation":738,"path":63,"seo":52274,"status":723,"stem":64,"tags":52275,"teaser":723,"__hash__":52277},"posts/blog/case-study/walkative-2-booking-platform-case-study.md",{"label":5,"color":50099},{"type":725,"value":51989,"toc":52245},[51990,51993,51997,52000,52004,52032,52036,52039,52059,52064,52067,52070,52073,52077,52106,52108,52142,52146,52151,52189,52193],[842,51991,51992],{},"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.",[863,51994,51996],{"id":51995},"about-walkative","About Walkative",[842,51998,51999],{},"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.",[863,52001,52003],{"id":52002},"platform-components","Platform Components",[1045,52005,52007,52011,52015,52020,52024,52028],{"className":52006},[1048,1049,1050,1051,1052],[1054,52008],{"description":52009,"icon":6395,"title":52010},"RESTful API architecture","Backend API",[1054,52012],{"description":52013,"icon":4855,"title":52014},"iOS & Android for guides","Mobile App",[1054,52016],{"description":52017,"icon":52018,"title":52019},"Cards, wallets & tipping","i-lucide-credit-card","Stripe Payments",[1054,52021],{"description":52022,"icon":1067,"title":52023},"TripAdvisor, Viator, GetYourGuide & more","OTA Integrations",[1054,52025],{"description":52026,"icon":3850,"title":52027},"Content management system","CMS",[1054,52029],{"description":52030,"icon":5365,"title":52031},"Embeddable HTML/JS widget","Booking Widget",[863,52033,52035],{"id":52034},"challenge","Challenge",[842,52037,52038],{},"Walkative needed a comprehensive booking platform to handle thousands of weekly reservations across multiple cities. The platform had to:",[958,52040,52041,52044,52047,52050,52053,52056],{},[961,52042,52043],{},"Automate the entire reservation process for free walking tours",[961,52045,52046],{},"Support multiple user roles: Tourists, Guides, Partners, Managers, and Administrators",[961,52048,52049],{},"Integrate with major OTA platforms to prevent overbooking",[961,52051,52052],{},"Provide a mobile application for guides to manage their tours on the go",[961,52054,52055],{},"Offer a Direct Booking Widget that could be embedded on partner websites",[961,52057,52058],{},"Handle complex availability management with backup guide functionality",[1572,52060,52061],{},[842,52062,52063],{},"The main challenge was creating a scalable system that could handle growing reservations while maintaining synchronization across multiple booking channels to prevent overbooking.",[863,52065,27663],{"id":52066},"solution",[842,52068,52069],{},"We developed Walkative 2.0 as a comprehensive cloud-based platform with the following key modules:",[3572,52071],{":items":52072},"[{\"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\"}]",[863,52074,52076],{"id":52075},"additional-features","Additional Features",[1045,52078,52081,52085,52089,52094,52098,52102],{"className":52079},[1048,1049,1765,52080,1051,1052],"lg:grid-cols-3",[1054,52082],{"description":52083,"icon":4845,"title":52084},"Tourists, Guides, Partners, Managers, Administrators","Multi-Role System",[1054,52086],{"description":52087,"icon":3850,"title":52088},"Tours, categories, FAQ, and text pages","CMS Module",[1054,52090],{"description":52091,"icon":52092,"title":52093},"Post-tour feedback system","i-lucide-star","Ratings & Comments",[1054,52095],{"description":52096,"icon":1062,"title":52097},"Promo codes, QR codes, commission tracking","Affiliate Module",[1054,52099],{"description":52100,"icon":52101,"title":18860},"Facebook, Google, Apple & email/password","i-lucide-log-in",[1054,52103],{"description":52104,"icon":7546,"title":52105},"Calendar, users, widgets, reports & settings","Admin Panel",[863,52107,8484],{"id":8483},[1045,52109,52111,52114,52117,52121,52126,52129,52133,52138],{"className":52110},[1048,50574,1050,50642,50239,1052],[1054,52112],{"description":52113,"icon":6395,"title":52010},"RESTful architecture",[1054,52115],{"description":52116,"icon":4855,"title":52014},"iOS & Android",[1054,52118],{"description":52119,"icon":52018,"title":52120},"Payments & tipping","Stripe",[1054,52122],{"description":52123,"icon":52124,"title":52125},"6 platform integrations","i-lucide-plug","OTA APIs",[1054,52127],{"description":52128,"icon":3850,"title":52027},"Content management",[1054,52130],{"description":52131,"icon":5365,"title":52132},"HTML/JavaScript","Widget",[1054,52134],{"description":52135,"icon":52136,"title":52137},"Email, SMS & Push","i-lucide-mail","Notifications",[1054,52139],{"description":52140,"icon":7495,"title":52141},"Social & email login","Auth",[863,52143,52145],{"id":52144},"outcome","Outcome",[1032,52147,52148],{},[842,52149,52150],{},"The platform successfully handles thousands of weekly reservations and continues to scale as Walkative expands to new cities and partners.",[958,52152,52153,52159,52165,52171,52177,52183],{},[961,52154,52155,52158],{},[996,52156,52157],{},"Automated reservations"," — reduced manual work across all booking channels",[961,52160,52161,52164],{},[996,52162,52163],{},"Zero overbooking"," — real-time OTA synchronization keeps availability in sync",[961,52166,52167,52170],{},[996,52168,52169],{},"Partner self-service"," — tourism companies manage tours independently",[961,52172,52173,52176],{},[996,52174,52175],{},"Mobile-first guides"," — dedicated app for on-the-go tour management",[961,52178,52179,52182],{},[996,52180,52181],{},"Flexible embedding"," — Direct Booking Widget works on any website",[961,52184,52185,52188],{},[996,52186,52187],{},"GDPR compliant"," — full data security and privacy compliance",[863,52190,52192],{"id":52191},"project-details","Project Details",[871,52194,52195,52203],{},[874,52196,52197],{},[877,52198,52199,52201],{},[880,52200],{},[880,52202],{},[887,52204,52205,52215,52225,52235],{},[877,52206,52207,52212],{},[892,52208,52209],{},[996,52210,52211],{},"Project Duration",[892,52213,52214],{},"12+ months",[877,52216,52217,52222],{},[892,52218,52219],{},[996,52220,52221],{},"Monthly Visitors",[892,52223,52224],{},"1M+ (within 12 months)",[877,52226,52227,52232],{},[892,52228,52229],{},[996,52230,52231],{},"Booking Channels",[892,52233,52234],{},"6+ OTA platforms + direct widget",[877,52236,52237,52242],{},[892,52238,52239],{},[996,52240,52241],{},"User Roles",[892,52243,52244],{},"5 (Tourist, Guide, Partner, Manager, Admin)",{"title":728,"searchDepth":729,"depth":729,"links":52246},[52247,52248,52249,52250,52251,52252,52253,52254],{"id":51995,"depth":729,"text":51996},{"id":52002,"depth":729,"text":52003},{"id":52034,"depth":729,"text":52035},{"id":52066,"depth":729,"text":27663},{"id":52075,"depth":729,"text":52076},{"id":8483,"depth":729,"text":8484},{"id":52144,"depth":729,"text":52145},{"id":52191,"depth":729,"text":52192},{"name":52256,"logo":52257},"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":52261,"hasLogo":738},"/images/case-studies/Walkative_MusicTech_Lab_Case_Study.webp",{"enabled":738,"items":52263},[52264,52266,52269,52271],{"text":52265,"icon":1067},"Cloud booking platform handling thousands of weekly reservations across European cities.",{"text":52267,"icon":52268},"Real-time sync with 6+ OTA platforms like Viator and GetYourGuide prevents overbooking.","i-lucide-refresh-cw",{"text":52270,"icon":3920},"1M+ monthly visitors within 12 months of launch.",{"text":52272,"icon":4855},"Dedicated Flutter mobile app for guides with tips, settlements, and tour management.",{},{"title":62,"description":52259},[4990,52276],"ecommerce","x_ODpClQFHdokCOwWM6emLgxc0pEUWkaqevL2oJx-YU",{"id":52279,"title":249,"authors":52280,"badge":723,"body":52283,"category":5678,"client":723,"date":53735,"description":53736,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":53737,"keyTakeaways":723,"meta":53739,"navigation":738,"path":250,"seo":53740,"status":723,"stem":251,"tags":53741,"teaser":723,"__hash__":53742},"posts/blog/newsletter/music-industry-tech-openings-may-2025.md",[52281],{"name":50093,"to":50094,"avatar":52282},{"src":50096},{"type":725,"value":52284,"toc":53692},[52285,52291,52303,52309,52314,52317,52323,52333,52338,52349,52351,52357,52367,52372,52413,52415,52420,52430,52435,52443,52445,52451,52461,52466,52471,52473,52479,52489,52494,52514,52516,52522,52532,52536,52550,52552,52558,52568,52573,52593,52595,52601,52611,52616,52627,52629,52635,52641,52649,52663,52665,52671,52681,52686,52694,52696,52702,52712,52717,52752,52754,52760,52770,52775,52791,52793,52799,52809,52815,52820,52822,52828,52838,52842,52850,52852,52858,52868,52874,52882,52884,52890,52900,52904,52908,52910,52916,52926,52930,52935,52937,52943,52953,52958,52975,52977,52983,52993,52998,53011,53013,53018,53028,53033,53038,53040,53046,53056,53061,53066,53068,53074,53084,53089,53097,53099,53105,53115,53120,53130,53132,53138,53148,53153,53161,53163,53169,53179,53183,53193,53195,53201,53211,53216,53223,53225,53230,53240,53245,53283,53285,53291,53301,53306,53311,53313,53319,53329,53333,53341,53343,53349,53355,53360,53365,53367,53373,53383,53388,53404,53406,53412,53422,53426,53439,53441,53447,53457,53462,53472,53474,53480,53490,53494,53505,53507,53513,53523,53528,53533,53535,53541,53551,53555,53563,53565,53571,53581,53586,53594,53596,53602,53612,53616,53623,53625,53631,53641,53645,53656,53658,53664,53674,53678],[1074,52286,52288],{"id":52287},"claimy",[996,52289,52290],{},"Claimy",[842,52292,52293,52297,52298],{},[846,52294,5669],{"href":52295,"rel":52296},"https://www.linkedin.com/company/claimy/",[850]," | ",[846,52299,52302],{"href":52300,"rel":52301},"https://www.notion.so/claimy/Founding-Engineer-Fullstack-Go-React-GCP-1f37f6328d3880a38abbde05367ce844",[850],"Careers",[842,52304,52305,52308],{},[996,52306,52307],{},"Location(s):"," 🇫🇷",[958,52310,52311],{},[961,52312,52313],{},"Founding Engineer – Fullstack (Go / React / GCP)",[842,52315,52316],{},"‍",[1074,52318,52320],{"id":52319},"trolley",[996,52321,52322],{},"Trolley",[842,52324,52325,52297,52329],{},[846,52326,5669],{"href":52327,"rel":52328},"https://www.linkedin.com/company/trolleyhq/",[850],[846,52330,52302],{"href":52331,"rel":52332},"https://trolley.com/careers/",[850],[842,52334,52335,52337],{},[996,52336,52307],{}," 🇨🇦",[958,52339,52340,52343,52346],{},[961,52341,52342],{},"Développeur Logiciel",[961,52344,52345],{},"DevOps Developer",[961,52347,52348],{},"Software Developer",[842,52350,52316],{},[1074,52352,52354],{"id":52353},"fender",[996,52355,52356],{},"Fender",[842,52358,52359,52297,52363],{},[846,52360,5669],{"href":52361,"rel":52362},"https://www.linkedin.com/company/fender-musical-instruments/",[850],[846,52364,52302],{"href":52365,"rel":52366},"https://boards.greenhouse.io/embed/job_board?for=fender",[850],[842,52368,52369,52371],{},[996,52370,52307],{}," 🇲🇽 🇺🇸 🇩🇪",[958,52373,52374,52377,52380,52383,52386,52389,52392,52395,52398,52401,52404,52407,52410],{},[961,52375,52376],{},"Software Engineer",[961,52378,52379],{},"Sr. Software Engineer",[961,52381,52382],{},"Sr. Software Engineer, DSP",[961,52384,52385],{},"Engineer, Environmental Health and Safety",[961,52387,52388],{},"Maintenance Supervisor",[961,52390,52391],{},"NPI Engineer",[961,52393,52394],{},"Sr. Manufacturing Engineer",[961,52396,52397],{},"Project Manager Manufacturing",[961,52399,52400],{},"Manager, Hardware Engineering",[961,52402,52403],{},"Mechanical Engineer I",[961,52405,52406],{},"Software Product Manager",[961,52408,52409],{},"Sr. Software Engineer, Applications",[961,52411,52412],{},"Staff Mechanical Engineer",[842,52414,52316],{},[1074,52416,52418],{"id":52417},"gigmit",[996,52419,52417],{},[842,52421,52422,52297,52426],{},[846,52423,5669],{"href":52424,"rel":52425},"https://www.linkedin.com/company/gigmit/",[850],[846,52427,52302],{"href":52428,"rel":52429},"https://www.gigmit.com/en/jobs#join",[850],[842,52431,52432,52434],{},[996,52433,52307],{}," 🇩🇪",[958,52436,52437,52440],{},[961,52438,52439],{},"Backend Engineer",[961,52441,52442],{},"Web-Development",[842,52444,52316],{},[1074,52446,52448],{"id":52447},"digico",[996,52449,52450],{},"DiGiCo",[842,52452,52453,52297,52457],{},[846,52454,5669],{"href":52455,"rel":52456},"https://www.linkedin.com/company/digico/",[850],[846,52458,52302],{"href":52459,"rel":52460},"https://digico.biz/recruitment/",[850],[842,52462,52463,52465],{},[996,52464,52307],{}," 🇬🇧",[958,52467,52468],{},[961,52469,52470],{},"Technical Support Engineer",[842,52472,52316],{},[1074,52474,52476],{"id":52475},"splash-music",[996,52477,52478],{},"Splash Music",[842,52480,52481,52297,52485],{},[846,52482,5669],{"href":52483,"rel":52484},"https://www.linkedin.com/company/splashmusicco/",[850],[846,52486,52302],{"href":52487,"rel":52488},"https://jobs.lever.co/splash-music",[850],[842,52490,52491,52493],{},[996,52492,52307],{}," 🇺🇸 🇦🇺",[958,52495,52496,52499,52502,52505,52508,52511],{},[961,52497,52498],{},"Full Stack Engineer Intern",[961,52500,52501],{},"Senior Full Stack Engineer",[961,52503,52504],{},"Senior Software Engineer",[961,52506,52507],{},"Applied ML Engineer",[961,52509,52510],{},"Roblox Developer",[961,52512,52513],{},"UX / UI Development Intern",[842,52515,52316],{},[1074,52517,52519],{"id":52518},"aicoustics",[996,52520,52521],{},"ai|coustics",[842,52523,52524,52297,52528],{},[846,52525,5669],{"href":52526,"rel":52527},"https://www.linkedin.com/company/ai-coustics/",[850],[846,52529,52302],{"href":52530,"rel":52531},"https://join.com/companies/ai-coustics",[850],[842,52533,52534,52434],{},[996,52535,52307],{},[958,52537,52538,52541,52544,52547],{},[961,52539,52540],{},"Experienced Backend Developer",[961,52542,52543],{},"Experienced Full-Stack Developer",[961,52545,52546],{},"Machine Learning Engineer",[961,52548,52549],{},"Platform Engineer",[842,52551,52316],{},[1074,52553,52555],{"id":52554},"beatport",[996,52556,52557],{},"Beatport",[842,52559,52560,52297,52564],{},[846,52561,5669],{"href":52562,"rel":52563},"https://www.linkedin.com/company/beatport/",[850],[846,52565,52302],{"href":52566,"rel":52567},"https://about.beatport.com/careers/",[850],[842,52569,52570,52572],{},[996,52571,52307],{}," 🇵🇱 🇺🇸 🇬🇧",[958,52574,52575,52578,52581,52584,52587,52590],{},[961,52576,52577],{},"Backend Data Engineer (Intermediate)",[961,52579,52580],{},"Front-end Software Engineer",[961,52582,52583],{},"Full-Stack Software Engineer",[961,52585,52586],{},"Site Reliability Engineer",[961,52588,52589],{},"Creative Designer (Junior/Mid Weight)",[961,52591,52592],{},"Multimedia Designer (Mid to Senior Weight)",[842,52594,52316],{},[1074,52596,52598],{"id":52597},"soundxyz",[996,52599,52600],{},"Sound.xyz",[842,52602,52603,52297,52607],{},[846,52604,5669],{"href":52605,"rel":52606},"https://www.linkedin.com/company/soundxyz/",[850],[846,52608,52302],{"href":52609,"rel":52610},"https://jobs.ashbyhq.com/sound.xyz",[850],[842,52612,52613,52615],{},[996,52614,52307],{}," 🇺🇸",[958,52617,52618,52621,52624],{},[961,52619,52620],{},"Staff Product Designer",[961,52622,52623],{},"Staff Software Engineer, Backend",[961,52625,52626],{},"Staff Software Engineer, Frontend",[842,52628,52316],{},[1074,52630,52632],{"id":52631},"soundcom",[996,52633,52634],{},"Sound.com",[842,52636,52637],{},[846,52638,52302],{"href":52639,"rel":52640},"https://www.sound.com/careers",[850],[842,52642,52643,52645,52646],{},[996,52644,52307],{}," 🌍 ",[964,52647,52648],{},"(Remote)",[958,52650,52651,52654,52657,52660],{},[961,52652,52653],{},"Senior Data Engineer",[961,52655,52656],{},"Senior Marketing Technician",[961,52658,52659],{},"Back End Developer",[961,52661,52662],{},"Full Stack Developer",[842,52664,52316],{},[1074,52666,52668],{"id":52667},"my-sheet-music-transcriptions",[996,52669,52670],{},"My Sheet Music Transcriptions",[842,52672,52673,52297,52677],{},[846,52674,5669],{"href":52675,"rel":52676},"https://www.linkedin.com/company/my-sheet-music-transcriptions/",[850],[846,52678,52302],{"href":52679,"rel":52680},"https://www.mysheetmusictranscriptions.com/careers",[850],[842,52682,52683,52685],{},[996,52684,52307],{}," 🇪🇸",[958,52687,52688,52691],{},[961,52689,52690],{},"Full Stack Software Engineer (mid/senior)",[961,52692,52693],{},"Music Informatics Engineer",[842,52695,52316],{},[1074,52697,52699],{"id":52698},"live-nation",[996,52700,52701],{},"Live Nation",[842,52703,52704,52297,52708],{},[846,52705,5669],{"href":52706,"rel":52707},"https://www.linkedin.com/company/livenationapac/",[850],[846,52709,52302],{"href":52710,"rel":52711},"https://livenation.wd1.myworkdayjobs.com/LNExternalSite?jobFamilyGroup=def6fe28d9a210a6e1ddb30d81afbf0e",[850],[842,52713,52714,52716],{},[996,52715,52307],{}," 🇺🇸 🇬🇷 🇵🇭 🇮🇳 🇬🇧",[958,52718,52719,52722,52725,52728,52731,52734,52737,52740,52743,52746,52749],{},[961,52720,52721],{},"Lead Software Engineer - SDK",[961,52723,52724],{},"Principal Engineer",[961,52726,52727],{},"Customer Solutions Developer, Sr.",[961,52729,52730],{},"Front End Developer and Designer",[961,52732,52733],{},"Senior Platform Engineer",[961,52735,52736],{},"Lead Backend Engineer",[961,52738,52739],{},"Platform DevOps Engineer",[961,52741,52742],{},"Platform Software Engineer",[961,52744,52745],{},"Senior Software Developer",[961,52747,52748],{},"Lead Software Developer",[961,52750,52751],{},"Senior Mobile Developer",[842,52753,52316],{},[1074,52755,52757],{"id":52756},"nbcuniversal",[996,52758,52759],{},"NBCUniversal",[842,52761,52762,52297,52766],{},[846,52763,5669],{"href":52764,"rel":52765},"https://www.linkedin.com/company/nbcuniversal-inc-/about/",[850],[846,52767,52302],{"href":52768,"rel":52769},"https://careers.smartrecruiters.com/NBCUniversal3",[850],[842,52771,52772,52774],{},[996,52773,52307],{}," 🇺🇸 🇸🇬",[958,52776,52777,52780,52783,52785,52788],{},[961,52778,52779],{},"Audio Systems Engineer",[961,52781,52782],{},"Product Designer",[961,52784,52504],{},[961,52786,52787],{},"Video Editing Intern",[961,52789,52790],{},"Associate Video Editor",[842,52792,52316],{},[1074,52794,52796],{"id":52795},"wve-media",[996,52797,52798],{},"WVE Media",[842,52800,52801,52297,52805],{},[846,52802,5669],{"href":52803,"rel":52804},"https://www.linkedin.com/company/wvemedia/",[850],[846,52806,52302],{"href":52807,"rel":52808},"https://www.musicbusinessworldwide.com/jobs/company/wve-media/#job-listings",[850],[842,52810,52811,52645,52813],{},[996,52812,52307],{},[964,52814,52648],{},[958,52816,52817],{},[961,52818,52819],{},"Full-Stack Developer & Operations Specialist",[842,52821,52316],{},[1074,52823,52825],{"id":52824},"phono-sounds-uk",[996,52826,52827],{},"Phono Sounds UK",[842,52829,52830,52297,52834],{},[846,52831,5669],{"href":52832,"rel":52833},"https://www.linkedin.com/showcase/phonosounds/",[850],[846,52835,52302],{"href":52836,"rel":52837},"https://www.entertainmentcareers.net/phono-sounds-uk/wordpress-developer/job/490651/",[850],[842,52839,52840,52465],{},[996,52841,52307],{},[958,52843,52844,52847],{},[961,52845,52846],{},"Web Design/Developer",[961,52848,52849],{},"WordPress Developer",[842,52851,52316],{},[1074,52853,52855],{"id":52854},"opendate",[996,52856,52857],{},"Opendate",[842,52859,52860,52297,52864],{},[846,52861,5669],{"href":52862,"rel":52863},"https://www.linkedin.com/company/opendate/",[850],[846,52865,52302],{"href":52866,"rel":52867},"https://www.opendate.io/careers",[850],[842,52869,52870,52645,52872],{},[996,52871,52307],{},[964,52873,52648],{},[958,52875,52876,52879],{},[961,52877,52878],{},"Senior DevOps Engineer",[961,52880,52881],{},"Senior Full Stack Ruby on Rails Software Engineer",[842,52883,52316],{},[1074,52885,52887],{"id":52886},"sony-music",[996,52888,52889],{},"Sony Music",[842,52891,52892,52297,52896],{},[846,52893,5669],{"href":52894,"rel":52895},"https://www.linkedin.com/company/sony-music-entertainment/",[850],[846,52897,52302],{"href":52898,"rel":52899},"https://www.sonymusic.co.uk/careers/jobs/",[850],[842,52901,52902,52465],{},[996,52903,52307],{},[958,52905,52906],{},[961,52907,52504],{},[842,52909,52316],{},[1074,52911,52913],{"id":52912},"elgato",[996,52914,52915],{},"Elgato",[842,52917,52918,52297,52922],{},[846,52919,5669],{"href":52920,"rel":52921},"https://www.linkedin.com/company/corsair/",[850],[846,52923,52302],{"href":52924,"rel":52925},"https://edix.fa.us2.oraclecloud.com/hcmUI/CandidateExperience/en/sites/CX_1/job/8301?utm_medium=jobshare",[850],[842,52927,52928,52434],{},[996,52929,52307],{},[958,52931,52932],{},[961,52933,52934],{},"Senior Front-End Engineer",[842,52936,52316],{},[1074,52938,52940],{"id":52939},"touchtunes",[996,52941,52942],{},"TouchTunes",[842,52944,52945,52297,52949],{},[846,52946,5669],{"href":52947,"rel":52948},"https://www.linkedin.com/company/touchtunes/",[850],[846,52950,52302],{"href":52951,"rel":52952},"https://jobs.dayforcehcm.com/en-US/octavegroup/CANDIDATEPORTAL",[850],[842,52954,52955,52957],{},[996,52956,52307],{}," 🇺🇸 🇨🇦",[958,52959,52960,52963,52966,52969,52972],{},[961,52961,52962],{},"Lead Technique Web / Staff Web Developer",[961,52964,52965],{},"Senior Backend Developer – Business Products",[961,52967,52968],{},"Senior Manager, Web & Backend Development",[961,52970,52971],{},"Backend Developer - Mobile",[961,52973,52974],{},"Engineering & Quality Manager",[842,52976,52316],{},[1074,52978,52980],{"id":52979},"ice",[996,52981,52982],{},"ICE",[842,52984,52985,52297,52989],{},[846,52986,5669],{"href":52987,"rel":52988},"https://www.linkedin.com/company/ice-ltd/",[850],[846,52990,52302],{"href":52991,"rel":52992},"https://www.iceservices.com/careers/",[850],[842,52994,52995,52997],{},[996,52996,52307],{}," 🇩🇪 🇬🇧",[958,52999,53000,53003,53006,53009],{},[961,53001,53002],{},"Senior Product Engineer",[961,53004,53005],{},"Senior Backend Engineer – Finance Royalty Processing",[961,53007,53008],{},"Senior Backend Engineer – Royalty Calculating",[961,53010,52733],{},[842,53012,52316],{},[1074,53014,53016],{"id":53015},"fuga",[996,53017,12942],{},[842,53019,53020,52297,53024],{},[846,53021,5669],{"href":53022,"rel":53023},"https://www.linkedin.com/company/fugamusic/",[850],[846,53025,52302],{"href":53026,"rel":53027},"https://fuga.com/jobs/",[850],[842,53029,53030,53032],{},[996,53031,52307],{}," 🇳🇱",[958,53034,53035],{},[961,53036,53037],{},"Senior Backend Engineer",[842,53039,52316],{},[1074,53041,53043],{"id":53042},"elektron-music-machines",[996,53044,53045],{},"Elektron Music Machines",[842,53047,53048,52297,53052],{},[846,53049,5669],{"href":53050,"rel":53051},"https://www.linkedin.com/company/elektron-music-machines/",[850],[846,53053,52302],{"href":53054,"rel":53055},"https://www.elektron.se/careers",[850],[842,53057,53058,53060],{},[996,53059,52307],{}," 🇸🇪",[958,53062,53063],{},[961,53064,53065],{},"Hardware Design Engineer",[842,53067,52316],{},[1074,53069,53071],{"id":53070},"yousician",[996,53072,53073],{},"Yousician",[842,53075,53076,52297,53080],{},[846,53077,5669],{"href":53078,"rel":53079},"https://www.linkedin.com/company/yousician/",[850],[846,53081,52302],{"href":53082,"rel":53083},"https://yousician.com/careers",[850],[842,53085,53086,53088],{},[996,53087,52307],{}," 🇫🇮 🇩🇪",[958,53090,53091,53094],{},[961,53092,53093],{},"Data Engineer",[961,53095,53096],{},"Product Data Scientist",[842,53098,52316],{},[1074,53100,53102],{"id":53101},"orfium",[996,53103,53104],{},"Orfium",[842,53106,53107,52297,53111],{},[846,53108,5669],{"href":53109,"rel":53110},"https://www.linkedin.com/company/orfium/",[850],[846,53112,52302],{"href":53113,"rel":53114},"https://www.orfium.com/careers/",[850],[842,53116,53117,53119],{},[996,53118,52307],{}," 🇬🇷 🇱🇰",[958,53121,53122,53125,53128],{},[961,53123,53124],{},"Associate Engineering Manager",[961,53126,53127],{},"Senior Engineering Manager",[961,53129,52504],{},[842,53131,52316],{},[1074,53133,53135],{"id":53134},"bmat-music-innovators",[996,53136,53137],{},"BMAT Music Innovators",[842,53139,53140,52297,53144],{},[846,53141,5669],{"href":53142,"rel":53143},"https://www.linkedin.com/company/bmat/",[850],[846,53145,52302],{"href":53146,"rel":53147},"https://www.bmat.com/careers/",[850],[842,53149,53150,53152],{},[996,53151,52307],{}," 🇺🇸 🇵🇪 🇦🇷 🇪🇸",[958,53154,53155,53158],{},[961,53156,53157],{},"Python Tech Lead",[961,53159,53160],{},"Software Engineering Intern",[842,53162,52316],{},[1074,53164,53166],{"id":53165},"landr",[996,53167,53168],{},"LANDR",[842,53170,53171,52297,53175],{},[846,53172,5669],{"href":53173,"rel":53174},"https://www.linkedin.com/company/landrmusic/",[850],[846,53176,52302],{"href":53177,"rel":53178},"https://landr.bamboohr.com/careers?source=aWQ9MTQ=",[850],[842,53180,53181,52337],{},[996,53182,52307],{},[958,53184,53185,53187,53190],{},[961,53186,52782],{},[961,53188,53189],{},"Software Developer C++",[961,53191,53192],{},"IT Technician",[842,53194,52316],{},[1074,53196,53198],{"id":53197},"songtradr",[996,53199,53200],{},"Songtradr",[842,53202,53203,52297,53207],{},[846,53204,5669],{"href":53205,"rel":53206},"https://www.linkedin.com/company/songtradr-inc/",[850],[846,53208,52302],{"href":53209,"rel":53210},"https://www.songtradr.com/careers",[850],[842,53212,53213,53215],{},[996,53214,52307],{}," 🇬🇧 🇺🇸 🌍",[958,53217,53218,53220],{},[961,53219,52504],{},[961,53221,53222],{},"Senior Tax Analyst",[842,53224,52316],{},[1074,53226,53227],{"id":24339},[996,53228,53229],{},"Meta",[842,53231,53232,52297,53236],{},[846,53233,5669],{"href":53234,"rel":53235},"https://www.linkedin.com/company/meta/",[850],[846,53237,52302],{"href":53238,"rel":53239},"https://www.metacareers.com/jobs?q=audio",[850],[842,53241,53242,53244],{},[996,53243,52307],{}," 🇺🇸 🇨🇳 🇬🇧",[958,53246,53247,53250,53253,53256,53259,53262,53265,53268,53271,53274,53277,53280],{},[961,53248,53249],{},"Software Engineering Manager, Audio",[961,53251,53252],{},"Software Engineer, Audio Embedded DSP – Reality Labs",[961,53254,53255],{},"Software Engineer, Audio SWE",[961,53257,53258],{},"Audio Mechanical Engineer",[961,53260,53261],{},"Audio System Engineer",[961,53263,53264],{},"Software Engineer, Audio Embedded DSP",[961,53266,53267],{},"Software Engineer, Audio Applied Scientist",[961,53269,53270],{},"ML and CV for Audio",[961,53272,53273],{},"Audio Software Engineer",[961,53275,53276],{},"Sound Designer",[961,53278,53279],{},"Audio Engineer (APAC)",[961,53281,53282],{},"Audio Engineer",[842,53284,52316],{},[1074,53286,53288],{"id":53287},"pro-sound-effects",[996,53289,53290],{},"Pro Sound Effects",[842,53292,53293,52297,53297],{},[846,53294,5669],{"href":53295,"rel":53296},"https://www.linkedin.com/company/pro-sound-effects/",[850],[846,53298,52302],{"href":53299,"rel":53300},"https://jobs.gusto.com/boards/pro-sound-effects-a587f3e8-bf47-4ee5-9387-a2a7cb1a4d5b",[850],[842,53302,53303,53305],{},[996,53304,52307],{}," 🌍",[958,53307,53308],{},[961,53309,53310],{},"Freelance C++ Back End Developer",[842,53312,52316],{},[1074,53314,53316],{"id":53315},"komi",[996,53317,53318],{},"KOMI",[842,53320,53321,52297,53325],{},[846,53322,5669],{"href":53323,"rel":53324},"https://www.linkedin.com/company/komii/",[850],[846,53326,52302],{"href":53327,"rel":53328},"https://komi.io/careers",[850],[842,53330,53331,52465],{},[996,53332,52307],{},[958,53334,53335,53338],{},[961,53336,53337],{},"Graphic Designer",[961,53339,53340],{},"Full Stack Engineer",[842,53342,52316],{},[1074,53344,53346],{"id":53345},"golba-music",[996,53347,53348],{},"Golba Music",[842,53350,53351],{},[846,53352,5669],{"href":53353,"rel":53354},"https://www.linkedin.com/jobs/view/4225689828/",[850],[842,53356,53357,53359],{},[996,53358,52307],{}," 🇵🇱",[958,53361,53362],{},[961,53363,53364],{},"Copyright & Royalties Manager",[842,53366,52316],{},[1074,53368,53370],{"id":53369},"eventbrite",[996,53371,53372],{},"Eventbrite",[842,53374,53375,52297,53379],{},[846,53376,5669],{"href":53377,"rel":53378},"https://www.linkedin.com/company/eventbrite/",[850],[846,53380,52302],{"href":53381,"rel":53382},"https://www.eventbritecareers.com/jobs/search",[850],[842,53384,53385,53387],{},[996,53386,52307],{}," 🌍 🇪🇸 🇮🇳 🇺🇸",[958,53389,53390,53393,53396,53399,53402],{},[961,53391,53392],{},"Product Designer II",[961,53394,53395],{},"Software Engineer II – Mobile (Fullstack)",[961,53397,53398],{},"Principal Software Engineer",[961,53400,53401],{},"Senior Software Engineer – Risk & Fraud",[961,53403,53392],{},[842,53405,52316],{},[1074,53407,53409],{"id":53408},"universal-music-group",[996,53410,53411],{},"Universal Music Group",[842,53413,53414,52297,53418],{},[846,53415,5669],{"href":53416,"rel":53417},"https://www.linkedin.com/company/universalmusicgroup/",[850],[846,53419,52302],{"href":53420,"rel":53421},"https://www.umusiccareers.com/gethired",[850],[842,53423,53424,52615],{},[996,53425,52307],{},[958,53427,53428,53431,53433,53436],{},[961,53429,53430],{},"Application Security Engineer",[961,53432,53340],{},[961,53434,53435],{},"Senior BI Engineer",[961,53437,53438],{},"Support Technician",[842,53440,52316],{},[1074,53442,53444],{"id":53443},"audience-republic",[996,53445,53446],{},"Audience Republic",[842,53448,53449,52297,53453],{},[846,53450,5669],{"href":53451,"rel":53452},"https://www.linkedin.com/company/audience-republic/",[850],[846,53454,52302],{"href":53455,"rel":53456},"https://audiencerep.bamboohr.com/careers",[850],[842,53458,53459,53461],{},[996,53460,52307],{}," 🇦🇺",[958,53463,53464,53467,53470],{},[961,53465,53466],{},"CTO (Startup)",[961,53468,53469],{},"Mid-Level Software Engineer (2 Positions)",[961,53471,52504],{},[842,53473,52316],{},[1074,53475,53477],{"id":53476},"vocana",[996,53478,53479],{},"Vocana",[842,53481,53482,52297,53486],{},[846,53483,5669],{"href":53484,"rel":53485},"https://www.linkedin.com/company/vocana/",[850],[846,53487,52302],{"href":53488,"rel":53489},"https://www.vocana.co/careers",[850],[842,53491,53492,52615],{},[996,53493,52307],{},[958,53495,53496,53499,53502],{},[961,53497,53498],{},"Frontend Developer",[961,53500,53501],{},"Backend Developer",[961,53503,53504],{},"UX Designer",[842,53506,52316],{},[1074,53508,53510],{"id":53509},"tomplay",[996,53511,53512],{},"Tomplay",[842,53514,53515,52297,53519],{},[846,53516,5669],{"href":53517,"rel":53518},"https://www.linkedin.com/company/tombooks/",[850],[846,53520,52302],{"href":53521,"rel":53522},"https://tomplay.com/pl/careers",[850],[842,53524,53525,53527],{},[996,53526,52307],{}," 🇨🇭",[958,53529,53530],{},[961,53531,53532],{},"Front-End Developer",[842,53534,52316],{},[1074,53536,53538],{"id":53537},"djoid",[996,53539,53540],{},"DJOID",[842,53542,53543,52297,53547],{},[846,53544,5669],{"href":53545,"rel":53546},"https://www.linkedin.com/company/djoid/",[850],[846,53548,52302],{"href":53549,"rel":53550},"https://www.djoid.io/company",[850],[842,53552,53553,52434],{},[996,53554,52307],{},[958,53556,53557,53560],{},[961,53558,53559],{},"DSP Engineer",[961,53561,53562],{},"Frontend Intern",[842,53564,52316],{},[1074,53566,53568],{"id":53567},"roli",[996,53569,53570],{},"ROLI",[842,53572,53573,52297,53577],{},[846,53574,5669],{"href":53575,"rel":53576},"https://www.linkedin.com/company/roli/jobs/",[850],[846,53578,52302],{"href":53579,"rel":53580},"https://roli.com/careers",[850],[842,53582,53583,53585],{},[996,53584,52307],{}," 🇬🇧 🇰🇷",[958,53587,53588,53591],{},[961,53589,53590],{},"Software QA Engineer",[961,53592,53593],{},"Mechanical Design Engineer",[842,53595,52316],{},[1074,53597,53599],{"id":53598},"the-focusrite-group",[996,53600,53601],{},"The Focusrite Group",[842,53603,53604,52297,53608],{},[846,53605,5669],{"href":53606,"rel":53607},"https://www.linkedin.com/company/focusrite/",[850],[846,53609,52302],{"href":53610,"rel":53611},"https://apply.workable.com/focusrite/#jobs",[850],[842,53613,53614,52465],{},[996,53615,52307],{},[958,53617,53618,53621],{},[961,53619,53620],{},"Embedded Software Engineer – Linea Research",[961,53622,53093],{},[842,53624,52316],{},[1074,53626,53628],{"id":53627},"collabhouse",[996,53629,53630],{},"Collabhouse",[842,53632,53633,52297,53637],{},[846,53634,5669],{"href":53635,"rel":53636},"https://www.linkedin.com/company/collabhouse/",[850],[846,53638,52302],{"href":53639,"rel":53640},"https://www.collabhouse.com/careers",[850],[842,53642,53643,53032],{},[996,53644,52307],{},[958,53646,53647,53650,53653],{},[961,53648,53649],{},"Senior Front-End Developer",[961,53651,53652],{},"Back-End / DevOps Engineer",[961,53654,53655],{},"UX/UI Designer",[842,53657,52316],{},[1074,53659,53661],{"id":53660},"inmusic",[996,53662,53663],{},"inMusic",[842,53665,53666,52297,53670],{},[846,53667,5669],{"href":53668,"rel":53669},"https://www.linkedin.com/company/inmusic-brands/",[850],[846,53671,52302],{"href":53672,"rel":53673},"https://inmusicbrands.pinpointhq.com/#js-careers-jobs-block",[850],[842,53675,53676,52465],{},[996,53677,52307],{},[958,53679,53680,53683,53686,53689],{},[961,53681,53682],{},"Embedded Linux Developer",[961,53684,53685],{},"Junior Workshop Engineer",[961,53687,53688],{},"Software Developer C++ Pro Audio",[961,53690,53691],{},"Technical Support Specialist Level 2",{"title":728,"searchDepth":729,"depth":729,"links":53693},[53694,53695,53696,53697,53698,53699,53700,53701,53702,53703,53704,53705,53706,53707,53708,53709,53710,53711,53712,53713,53714,53715,53716,53717,53718,53719,53720,53721,53722,53723,53724,53725,53726,53727,53728,53729,53730,53731,53732,53733,53734],{"id":52287,"depth":1112,"text":52290},{"id":52319,"depth":1112,"text":52322},{"id":52353,"depth":1112,"text":52356},{"id":52417,"depth":1112,"text":52417},{"id":52447,"depth":1112,"text":52450},{"id":52475,"depth":1112,"text":52478},{"id":52518,"depth":1112,"text":52521},{"id":52554,"depth":1112,"text":52557},{"id":52597,"depth":1112,"text":52600},{"id":52631,"depth":1112,"text":52634},{"id":52667,"depth":1112,"text":52670},{"id":52698,"depth":1112,"text":52701},{"id":52756,"depth":1112,"text":52759},{"id":52795,"depth":1112,"text":52798},{"id":52824,"depth":1112,"text":52827},{"id":52854,"depth":1112,"text":52857},{"id":52886,"depth":1112,"text":52889},{"id":52912,"depth":1112,"text":52915},{"id":52939,"depth":1112,"text":52942},{"id":52979,"depth":1112,"text":52982},{"id":53015,"depth":1112,"text":12942},{"id":53042,"depth":1112,"text":53045},{"id":53070,"depth":1112,"text":53073},{"id":53101,"depth":1112,"text":53104},{"id":53134,"depth":1112,"text":53137},{"id":53165,"depth":1112,"text":53168},{"id":53197,"depth":1112,"text":53200},{"id":24339,"depth":1112,"text":53229},{"id":53287,"depth":1112,"text":53290},{"id":53315,"depth":1112,"text":53318},{"id":53345,"depth":1112,"text":53348},{"id":53369,"depth":1112,"text":53372},{"id":53408,"depth":1112,"text":53411},{"id":53443,"depth":1112,"text":53446},{"id":53476,"depth":1112,"text":53479},{"id":53509,"depth":1112,"text":53512},{"id":53537,"depth":1112,"text":53540},{"id":53567,"depth":1112,"text":53570},{"id":53598,"depth":1112,"text":53601},{"id":53627,"depth":1112,"text":53630},{"id":53660,"depth":1112,"text":53663},"2025-05-26T00:00:00.000Z","Curated list of tech job openings in the music industry for May 2025. Discover roles at music startups, labels, and streaming platforms.",{"src":53738},"/images/blog/musictechlab_blog_music-industry-tech-openings-may-2025-update.webp",{},{"title":249,"description":53736},[5678,5523],"mMkI5fuTo9oZ4Avw4hpMe4sQq390JFDEdXo4ba-FD44",{"id":53744,"title":213,"authors":53745,"badge":723,"body":53748,"category":5678,"client":723,"date":55133,"description":55134,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":55135,"keyTakeaways":723,"meta":55137,"navigation":738,"path":214,"seo":55138,"status":723,"stem":215,"tags":55139,"teaser":723,"__hash__":55140},"posts/blog/newsletter/music-industry-tech-openings-april-2025-update.md",[53746],{"name":50093,"to":50094,"avatar":53747},{"src":50096},{"type":725,"value":53749,"toc":55087},[53750,53756,53766,53769,53774,53776,53782,53792,53795,53806,53808,53814,53820,53823,53828,53830,53836,53846,53849,53880,53882,53888,53898,53901,53906,53908,53914,53924,53927,53935,53937,53943,53953,53956,53970,53972,53978,53988,53991,54002,54004,54010,54020,54022,54027,54029,54035,54045,54047,54064,54066,54072,54082,54084,54094,54096,54101,54111,54114,54119,54121,54127,54137,54140,54155,54157,54161,54169,54171,54175,54177,54183,54193,54196,54201,54203,54209,54219,54222,54227,54229,54235,54245,54247,54258,54260,54266,54276,54279,54287,54289,54295,54305,54307,54311,54313,54317,54325,54328,54332,54334,54340,54350,54352,54357,54359,54363,54371,54374,54381,54383,54387,54395,54398,54406,54408,54414,54424,54427,54461,54463,54469,54479,54482,54491,54493,54499,54509,54512,54517,54519,54525,54535,54538,54546,54548,54554,54564,54566,54583,54585,54591,54601,54604,54624,54626,54632,54642,54645,54659,54661,54667,54677,54679,54690,54692,54698,54708,54711,54724,54726,54732,54742,54744,54749,54751,54757,54767,54769,54777,54779,54785,54795,54797,54804,54806,54812,54822,54825,54838,54840,54846,54856,54859,54875,54877,54883,54893,54895,54912,54914,54918,54926,54928,54934,54936,54942,54948,54950,54957,54959,54965,54975,54978,55002,55004,55010,55020,55023,55028,55030,55036,55046,55048,55053,55055,55059,55068,55071,55085],[1074,53751,53753],{"id":53752},"prisma-music-groupe",[996,53754,53755],{},"Prisma Music Groupe",[842,53757,53758,52297,53762],{},[846,53759,5669],{"href":53760,"rel":53761},"https://www.linkedin.com/company/prisma-music-group/",[850],[846,53763,52302],{"href":53764,"rel":53765},"https://www.prismamusicgroup.com/jobs/",[850],[842,53767,53768],{},"Location(s): 🇬🇧",[958,53770,53771],{},[961,53772,53773],{},"Chief Technology Officer / Digital Transformation Officer",[842,53775,52316],{},[1074,53777,53779],{"id":53778},"epic-games",[996,53780,53781],{},"Epic Games",[842,53783,53784,52297,53788],{},[846,53785,5669],{"href":53786,"rel":53787},"https://www.linkedin.com/company/epic-games/",[850],[846,53789,52302],{"href":53790,"rel":53791},"https://www.epicgames.com/site/en-US/careers/jobs?department=Audio%20%26%20Design&page=1",[850],[842,53793,53794],{},"Location(s): 🇺🇸",[958,53796,53797,53800,53803],{},[961,53798,53799],{},"Audio Designer",[961,53801,53802],{},"Audio Designer (Voiceover)",[961,53804,53805],{},"Audio Director",[842,53807,52316],{},[1074,53809,53811],{"id":53810},"reloop",[996,53812,53813],{},"Reloop",[842,53815,53816],{},[846,53817,5669],{"href":53818,"rel":53819},"https://www.reloop.com/jobcenter",[850],[842,53821,53822],{},"Location(s): 🇩🇪 🌍",[958,53824,53825],{},[961,53826,53827],{},"IT Specialist for Application Development",[842,53829,52316],{},[1074,53831,53833],{"id":53832},"soundhound-ai",[996,53834,53835],{},"SoundHound AI",[842,53837,53838,52297,53842],{},[846,53839,5669],{"href":53840,"rel":53841},"https://www.linkedin.com/company/soundhoundai/",[850],[846,53843,52302],{"href":53844,"rel":53845},"https://www.soundhound.com/careers",[850],[842,53847,53848],{},"Location(s): 🇩🇪 🇮🇳 🇨🇦 🇺🇦",[958,53850,53851,53854,53857,53860,53863,53866,53869,53871,53874,53877],{},[961,53852,53853],{},"Senior Machine Learning Engineer, ASR",[961,53855,53856],{},"Software Engineer, Voice AI (Kannada)",[961,53858,53859],{},"Student Software Engineer",[961,53861,53862],{},"Project Manager, ASR Data",[961,53864,53865],{},"Senior Full-Stack Engineer, Smart Answering",[961,53867,53868],{},"Senior Software Engineer, Music Search",[961,53870,53868],{},[961,53872,53873],{},"Software Engineer, Voice AI (Telugu)",[961,53875,53876],{},"Product Support Engineer II",[961,53878,53879],{},"Principal Research Engineer, Generative AI",[842,53881,52316],{},[1074,53883,53885],{"id":53884},"mirelo-ai",[996,53886,53887],{},"Mirelo AI",[842,53889,53890,52297,53894],{},[846,53891,5669],{"href":53892,"rel":53893},"https://www.linkedin.com/company/mirelo-ai/",[850],[846,53895,52302],{"href":53896,"rel":53897},"https://www.linkedin.com/jobs/view/4191081918",[850],[842,53899,53900],{},"Location(s): 🇩🇪",[958,53902,53903],{},[961,53904,53905],{},"Senior Full-Stack AI Engineer & Product Tech Lead",[842,53907,52316],{},[1074,53909,53911],{"id":53910},"gaudio-lab",[996,53912,53913],{},"Gaudio Lab",[842,53915,53916,52297,53920],{},[846,53917,5669],{"href":53918,"rel":53919},"https://www.linkedin.com/company/gaudiolab/",[850],[846,53921,52302],{"href":53922,"rel":53923},"https://www.gaudiolab.com/careers/open-position",[850],[842,53925,53926],{},"Location(s): 🇰🇷",[958,53928,53929,53932],{},[961,53930,53931],{},"Backend Software Engineer",[961,53933,53934],{},"AI SDK Software Engineer",[842,53936,52316],{},[1074,53938,53940],{"id":53939},"arturia",[996,53941,53942],{},"Arturia",[842,53944,53945,52297,53949],{},[846,53946,5669],{"href":53947,"rel":53948},"https://www.linkedin.com/company/arturia/",[850],[846,53950,52302],{"href":53951,"rel":53952},"https://jobs.arturia.com/join-us",[850],[842,53954,53955],{},"Location(s): 🇫🇷",[958,53957,53958,53961,53964,53967],{},[961,53959,53960],{},"Product Designer – Software Instruments & Effects",[961,53962,53963],{},"Build & CI/CD Engineer",[961,53965,53966],{},"Senior Back-End Lead Developer",[961,53968,53969],{},"Tech Lead C++ H/F",[842,53971,52316],{},[1074,53973,53975],{"id":53974},"zaiks",[996,53976,53977],{},"ZAiKS",[842,53979,53980,52297,53984],{},[846,53981,5669],{"href":53982,"rel":53983},"https://www.linkedin.com/company/zaiks/",[850],[846,53985,52302],{"href":53986,"rel":53987},"https://zaiks.org.pl/o-nas/praca-w-zaiks-ie",[850],[842,53989,53990],{},"Location(s): 🇵🇱",[958,53992,53993,53996,53999],{},[961,53994,53995],{},"Java Developer",[961,53997,53998],{},"Junior Specialist in Music Data Entry and Identification",[961,54000,54001],{},"System Analyst",[842,54003,52316],{},[1074,54005,54007],{"id":54006},"biletynapl",[996,54008,54009],{},"biletyna.pl",[842,54011,54012,52297,54016],{},[846,54013,5669],{"href":54014,"rel":54015},"https://www.linkedin.com/company/biletyna/",[850],[846,54017,52302],{"href":54018,"rel":54019},"https://biletyna.pl/static/kariera",[850],[842,54021,53990],{},[958,54023,54024],{},[961,54025,54026],{},"Senior Frontend Developer",[842,54028,52316],{},[1074,54030,54032],{"id":54031},"eventim",[996,54033,54034],{},"Eventim",[842,54036,54037,52297,54041],{},[846,54038,5669],{"href":54039,"rel":54040},"https://www.linkedin.com/company/cts-eventim-ag/",[850],[846,54042,52302],{"href":54043,"rel":54044},"https://softwareentwicklung-karriere.eventim.de/en/",[850],[842,54046,53900],{},[958,54048,54049,54052,54055,54058,54061],{},[961,54050,54051],{},"Senior Frontend Engineer, Cross Product Services",[961,54053,54054],{},"Software Engineer Vue.js, EVENTIM.Light",[961,54056,54057],{},"Senior Backend Engineer, EVENTIM Webshop",[961,54059,54060],{},"Werkstudent Software, Tester / Quality Assurance",[961,54062,54063],{},"Senior Mobile Developer, Android",[842,54065,52316],{},[1074,54067,54069],{"id":54068},"vobile",[996,54070,54071],{},"Vobile",[842,54073,54074,52297,54078],{},[846,54075,5669],{"href":54076,"rel":54077},"https://www.linkedin.com/company/vobile/",[850],[846,54079,52302],{"href":54080,"rel":54081},"https://us.vobile.com/careers",[850],[842,54083,53794],{},[958,54085,54086,54089,54091],{},[961,54087,54088],{},"AIGC Research Engineer",[961,54090,53340],{},[961,54092,54093],{},"DevOps Engineer",[842,54095,52316],{},[1074,54097,54099],{"id":54098},"distrokid",[996,54100,10406],{},[842,54102,54103,52297,54107],{},[846,54104,5669],{"href":54105,"rel":54106},"https://www.linkedin.com/company/distrokid/",[850],[846,54108,52302],{"href":54109,"rel":54110},"https://distrokid.teamtailor.com/jobs",[850],[842,54112,54113],{},"Location(s): 🇬🇧 🇪🇺 🌍",[958,54115,54116],{},[961,54117,54118],{},"Senior Product Designer",[842,54120,52316],{},[1074,54122,54124],{"id":54123},"unitedmasters",[996,54125,54126],{},"UnitedMasters",[842,54128,54129,52297,54133],{},[846,54130,5669],{"href":54131,"rel":54132},"https://www.linkedin.com/company/unitedmasters/",[850],[846,54134,52302],{"href":54135,"rel":54136},"https://job-boards.greenhouse.io/unitedmasterstranslation",[850],[842,54138,54139],{},"Location(s): 🇺🇸 🇨🇦",[958,54141,54142,54145,54148,54150,54152],{},[961,54143,54144],{},"Senior Software Engineer, Web",[961,54146,54147],{},"Software Engineer, Backend",[961,54149,52623],{},[961,54151,52782],{},[961,54153,54154],{},"Video Operations Lead",[842,54156,52316],{},[1074,54158,54159],{"id":53197},[996,54160,53200],{},[842,54162,54163,52297,54166],{},[846,54164,5669],{"href":53205,"rel":54165},[850],[846,54167,52302],{"href":53209,"rel":54168},[850],[842,54170,54113],{},[958,54172,54173],{},[961,54174,52504],{},[842,54176,52316],{},[1074,54178,54180],{"id":54179},"too-lost",[996,54181,54182],{},"Too Lost",[842,54184,54185,52297,54189],{},[846,54186,5669],{"href":54187,"rel":54188},"https://www.linkedin.com/company/too-lost/",[850],[846,54190,52302],{"href":54191,"rel":54192},"https://toolost.com/careers",[850],[842,54194,54195],{},"Location(s): 🇮🇸",[958,54197,54198],{},[961,54199,54200],{},"Infrastructure & Security Engineer",[842,54202,52316],{},[1074,54204,54206],{"id":54205},"symphonic",[996,54207,54208],{},"Symphonic",[842,54210,54211,52297,54215],{},[846,54212,5669],{"href":54213,"rel":54214},"https://www.linkedin.com/company/symphonic-distribution/",[850],[846,54216,52302],{"href":54217,"rel":54218},"https://symphonicdist.bamboohr.com/careers",[850],[842,54220,54221],{},"Location(s): 🌍",[958,54223,54224],{},[961,54225,54226],{},"Software Developer (PHP/Laravel)",[842,54228,52316],{},[1074,54230,54232],{"id":54231},"audacy",[996,54233,54234],{},"Audacy",[842,54236,54237,52297,54241],{},[846,54238,5669],{"href":54239,"rel":54240},"https://www.linkedin.com/company/audacy-inc/",[850],[846,54242,52302],{"href":54243,"rel":54244},"https://careers-audacy.icims.com/jobs/search?ss=1&searchCategory=8723",[850],[842,54246,53794],{},[958,54248,54249,54252,54255],{},[961,54250,54251],{},"Chief Engineer",[961,54253,54254],{},"Technical Engineer",[961,54256,54257],{},"Remote Broadcast Technician",[842,54259,52316],{},[1074,54261,54263],{"id":54262},"podcastle",[996,54264,54265],{},"Podcastle",[842,54267,54268,52297,54272],{},[846,54269,5669],{"href":54270,"rel":54271},"https://www.linkedin.com/company/podcastle-ai/",[850],[846,54273,52302],{"href":54274,"rel":54275},"https://podcastle.ai/careers",[850],[842,54277,54278],{},"Location(s): 🇦🇲",[958,54280,54281,54284],{},[961,54282,54283],{},"UI Designer",[961,54285,54286],{},"Marketing Designer",[842,54288,52316],{},[1074,54290,54292],{"id":54291},"linkfire",[996,54293,54294],{},"Linkfire",[842,54296,54297,52297,54301],{},[846,54298,5669],{"href":54299,"rel":54300},"https://www.linkedin.com/company/linkfire/",[850],[846,54302,52302],{"href":54303,"rel":54304},"https://www.linkfire.com/careers#JOIN",[850],[842,54306,54221],{},[958,54308,54309],{},[961,54310,52934],{},[842,54312,52316],{},[1074,54314,54315],{"id":53015},[996,54316,12942],{},[842,54318,54319,52297,54322],{},[846,54320,5669],{"href":53022,"rel":54321},[850],[846,54323,52302],{"href":53026,"rel":54324},[850],[842,54326,54327],{},"Location(s): 🇳🇱",[958,54329,54330],{},[961,54331,52653],{},[842,54333,52316],{},[1074,54335,54337],{"id":54336},"tutteo",[996,54338,54339],{},"Tutteo",[842,54341,54342,52297,54346],{},[846,54343,5669],{"href":54344,"rel":54345},"https://www.linkedin.com/company/tutteo/",[850],[846,54347,52302],{"href":54348,"rel":54349},"https://www.tutteo.com/careers/",[850],[842,54351,54221],{},[958,54353,54354],{},[961,54355,54356],{},"Lead Product Designer",[842,54358,52316],{},[1074,54360,54361],{"id":53134},[996,54362,53137],{},[842,54364,54365,52297,54368],{},[846,54366,5669],{"href":53142,"rel":54367},[850],[846,54369,52302],{"href":53146,"rel":54370},[850],[842,54372,54373],{},"Location(s): 🇮🇹",[958,54375,54376,54378],{},[961,54377,52662],{},[961,54379,54380],{},"Ruby on Rails Developer",[842,54382,52316],{},[1074,54384,54385],{"id":53165},[996,54386,53168],{},[842,54388,54389,52297,54392],{},[846,54390,5669],{"href":53173,"rel":54391},[850],[846,54393,52302],{"href":53177,"rel":54394},[850],[842,54396,54397],{},"Location(s): 🇨🇦",[958,54399,54400,54402,54404],{},[961,54401,52782],{},[961,54403,53189],{},[961,54405,53498],{},[842,54407,52316],{},[1074,54409,54411],{"id":54410},"siriusxm",[996,54412,54413],{},"SiriusXM",[842,54415,54416,52297,54420],{},[846,54417,5669],{"href":54418,"rel":54419},"https://www.linkedin.com/company/siriusxm/",[850],[846,54421,52302],{"href":54422,"rel":54423},"https://careers.siriusxm.com/careers/jobs?page=1",[850],[842,54425,54426],{},"Location(s): 🇮🇪",[958,54428,54429,54432,54435,54438,54441,54444,54447,54449,54452,54455,54458],{},[961,54430,54431],{},"Senior Technician, PSO",[961,54433,54434],{},"Senior Software Test Engineer",[961,54436,54437],{},"Test/QA Engineer III",[961,54439,54440],{},"Software Engineer III",[961,54442,54443],{},"Director, Cloud Engineering",[961,54445,54446],{},"Senior SW Test Development",[961,54448,52504],{},[961,54450,54451],{},"Software Engineer III, Automotive",[961,54453,54454],{},"Software Development Engineer in Test II",[961,54456,54457],{},"Senior SW Test Development: Tools Developer",[961,54459,54460],{},"Senior Software Engineer Android",[842,54462,52316],{},[1074,54464,54466],{"id":54465},"luminate",[996,54467,54468],{},"Luminate",[842,54470,54471,52297,54475],{},[846,54472,5669],{"href":54473,"rel":54474},"https://www.linkedin.com/company/luminatedata/",[850],[846,54476,52302],{"href":54477,"rel":54478},"https://luminatedata.com/careers/",[850],[842,54480,54481],{},"Location(s): 🇺🇸 🇫🇷",[958,54483,54484,54486,54488],{},[961,54485,52546],{},[961,54487,52653],{},[961,54489,54490],{},"Staff Software Engineer",[842,54492,52316],{},[1074,54494,54496],{"id":54495},"bandlab",[996,54497,54498],{},"BandLab",[842,54500,54501,52297,54505],{},[846,54502,5669],{"href":54503,"rel":54504},"https://www.linkedin.com/company/bandlab/",[850],[846,54506,52302],{"href":54507,"rel":54508},"https://www.bandlab.com/careers",[850],[842,54510,54511],{},"Location(s): 🇸🇬",[958,54513,54514],{},[961,54515,54516],{},"Backend Engineer, Artist Services team",[842,54518,52316],{},[1074,54520,54522],{"id":54521},"winamp",[996,54523,54524],{},"Winamp",[842,54526,54527,52297,54531],{},[846,54528,5669],{"href":54529,"rel":54530},"https://www.linkedin.com/company/winamp/",[850],[846,54532,52302],{"href":54533,"rel":54534},"https://winamp.com/jobs",[850],[842,54536,54537],{},"Location(s): 🇧🇪",[958,54539,54540,54543],{},[961,54541,54542],{},"Front-end Web Developer",[961,54544,54545],{},"Lead Back-end Developer",[842,54547,52316],{},[1074,54549,54551],{"id":54550},"aeg",[996,54552,54553],{},"AEG",[842,54555,54556,52297,54560],{},[846,54557,5669],{"href":54558,"rel":54559},"https://www.linkedin.com/company/aeg/",[850],[846,54561,52302],{"href":54562,"rel":54563},"https://aegworldwide.com/careers/job-search?field_job_departments_target_id=487606&field_job_locations_target_id=All&field_job_employee_types_target_id=All&title=",[850],[842,54565,53794],{},[958,54567,54568,54571,54574,54577,54580],{},[961,54569,54570],{},"Product Manager",[961,54572,54573],{},"Systems Developer",[961,54575,54576],{},"Systems Engineer",[961,54578,54579],{},"Director of Product, Enterprise – AXS",[961,54581,54582],{},"Director Cybersecurity Operations Center (SOC)",[842,54584,52316],{},[1074,54586,54588],{"id":54587},"avid-technology",[996,54589,54590],{},"Avid Technology",[842,54592,54593,52297,54597],{},[846,54594,5669],{"href":54595,"rel":54596},"https://www.linkedin.com/company/avid-technology/",[850],[846,54598,52302],{"href":54599,"rel":54600},"https://avid.wd5.myworkdayjobs.com/AVID",[850],[842,54602,54603],{},"Location(s): 🇵🇭 🇵🇱 🇨🇦",[958,54605,54606,54609,54612,54615,54618,54621],{},[961,54607,54608],{},"Technical Support Engineer – Audio (Sibelius)",[961,54610,54611],{},"Audio Technical Support",[961,54613,54614],{},"Data Analyst",[961,54616,54617],{},"Sr. HRIS Administrator – Global Enterprise Applications",[961,54619,54620],{},"Sibelius Software Developer – Junior position",[961,54622,54623],{},"Senior Full-stack Developer",[842,54625,52316],{},[1074,54627,54629],{"id":54628},"antelope-audio",[996,54630,54631],{},"Antelope Audio",[842,54633,54634,52297,54638],{},[846,54635,5669],{"href":54636,"rel":54637},"https://www.linkedin.com/company/antelope-audio/",[850],[846,54639,52302],{"href":54640,"rel":54641},"https://en.antelopeaudio.com/careers/",[850],[842,54643,54644],{},"Location(s): 🇧🇬",[958,54646,54647,54650,54653,54656],{},[961,54648,54649],{},"Embedded C/C++ Engineer – Advanced Audio Electronics",[961,54651,54652],{},"Audio Product Electronics Engineer",[961,54654,54655],{},"Senior Python Developer",[961,54657,54658],{},"Senior C/C++ Audio Driver Developer",[842,54660,52316],{},[1074,54662,54664],{"id":54663},"eventide-audio",[996,54665,54666],{},"Eventide Audio",[842,54668,54669,52297,54673],{},[846,54670,5669],{"href":54671,"rel":54672},"https://www.linkedin.com/company/eventide/",[850],[846,54674,52302],{"href":54675,"rel":54676},"https://www.eventideaudio.com/employmen",[850],[842,54678,53794],{},[958,54680,54681,54684,54687],{},[961,54682,54683],{},"Audio Internship",[961,54685,54686],{},"Junior System Administrator",[961,54688,54689],{},"Electronics Technician",[842,54691,52316],{},[1074,54693,54695],{"id":54694},"audinate",[996,54696,54697],{},"Audinate",[842,54699,54700,52297,54704],{},[846,54701,5669],{"href":54702,"rel":54703},"https://www.linkedin.com/company/audinate/",[850],[846,54705,52302],{"href":54706,"rel":54707},"https://jobs.lever.co/audinate",[850],[842,54709,54710],{},"Location(s): 🇵🇭 🇲🇽 🇧🇪",[958,54712,54713,54715,54718,54721],{},[961,54714,52662],{},[961,54716,54717],{},"Embedded Software Engineer",[961,54719,54720],{},"Software Engineering Team Lead",[961,54722,54723],{},"Technical Support Specialist",[842,54725,52316],{},[1074,54727,54729],{"id":54728},"ohm-systems",[996,54730,54731],{},"Ohm Systems",[842,54733,54734,52297,54738],{},[846,54735,5669],{"href":54736,"rel":54737},"https://www.linkedin.com/company/ohm-systems/",[850],[846,54739,52302],{"href":54740,"rel":54741},"https://www.ohmsystems.com/employment",[850],[842,54743,53794],{},[958,54745,54746],{},[961,54747,54748],{},"Low Voltage / Audio Visual Systems Technician",[842,54750,52316],{},[1074,54752,54754],{"id":54753},"ik-multimedia",[996,54755,54756],{},"IK Multimedia",[842,54758,54759,52297,54763],{},[846,54760,5669],{"href":54761,"rel":54762},"https://www.linkedin.com/company/ikmultimedia/",[850],[846,54764,52302],{"href":54765,"rel":54766},"https://www.ikmultimedia.com/careers/",[850],[842,54768,54373],{},[958,54770,54771,54774],{},[961,54772,54773],{},"Electronics Hardware Engineer",[961,54775,54776],{},"Firmware Engineer",[842,54778,52316],{},[1074,54780,54782],{"id":54781},"soundtoys",[996,54783,54784],{},"Soundtoys",[842,54786,54787,52297,54791],{},[846,54788,5669],{"href":54789,"rel":54790},"https://www.linkedin.com/company/soundtoys/",[850],[846,54792,52302],{"href":54793,"rel":54794},"https://www.soundtoys.com/jobs/",[850],[842,54796,53794],{},[958,54798,54799,54801],{},[961,54800,52348],{},[961,54802,54803],{},"Audio DSP Engineer",[842,54805,52316],{},[1074,54807,54809],{"id":54808},"the-audio-programmer",[996,54810,54811],{},"The Audio Programmer",[842,54813,54814,52297,54818],{},[846,54815,5669],{"href":54816,"rel":54817},"https://www.linkedin.com/company/theaudioprogrammer/",[850],[846,54819,52302],{"href":54820,"rel":54821},"https://www.theaudioprogrammer.com/careers",[850],[842,54823,54824],{},"Location(s): 🌍 Remote, 🇬🇧",[958,54826,54827,54830,54832,54835],{},[961,54828,54829],{},"DSP Engineer – Acoustic Analysis",[961,54831,53340],{},[961,54833,54834],{},"Embedded Firmware Engineer – Pro Audio",[961,54836,54837],{},"Mobile Developer – Music Technology",[842,54839,52316],{},[1074,54841,54843],{"id":54842},"universal-audio",[996,54844,54845],{},"Universal Audio",[842,54847,54848,52297,54852],{},[846,54849,5669],{"href":54850,"rel":54851},"https://www.linkedin.com/company/universal-audio/",[850],[846,54853,52302],{"href":54854,"rel":54855},"https://job-boards.greenhouse.io/universalaudio",[850],[842,54857,54858],{},"Location(s): 🌍 🇺🇸",[958,54860,54861,54864,54866,54869,54872],{},[961,54862,54863],{},"Engineering – Software",[961,54865,52653],{},[961,54867,54868],{},"Engineering – Hardware",[961,54870,54871],{},"Engineering – Mechanical",[961,54873,54874],{},"Product Development",[842,54876,52316],{},[1074,54878,54880],{"id":54879},"bmg",[996,54881,54882],{},"BMG",[842,54884,54885,52297,54889],{},[846,54886,5669],{"href":54887,"rel":54888},"https://www.linkedin.com/company/bmg-the-new-music-company/",[850],[846,54890,52302],{"href":54891,"rel":54892},"https://jobsearch.createyourowncareer.com/BMG/search/?createNewAlert=false&q=&locationsearch=",[850],[842,54894,53900],{},[958,54896,54897,54900,54903,54906,54909],{},[961,54898,54899],{},"Lead Data Scientist AI and Forecasting",[961,54901,54902],{},"Business Analyst AI Automation",[961,54904,54905],{},"Lead Architect AI Automation",[961,54907,54908],{},"Machine Learning Engineer AI Automation (LLMs)",[961,54910,54911],{},"Platform Engineer Agentic Systems",[842,54913,52316],{},[1074,54915,54916],{"id":53598},[996,54917,53601],{},[842,54919,54920,52297,54923],{},[846,54921,5669],{"href":53606,"rel":54922},[850],[846,54924,52302],{"href":53610,"rel":54925},[850],[842,54927,53768],{},[958,54929,54930,54932],{},[961,54931,53620],{},[961,54933,53093],{},[842,54935,52316],{},[1074,54937,54939],{"id":54938},"neumannberlin",[996,54940,54941],{},"NEUMANN.BERLIN",[842,54943,54944],{},[846,54945,52302],{"href":54946,"rel":54947},"https://www.neumann.com/en-us/company/jobs",[850],[842,54949,53900],{},[958,54951,54952,54954],{},[961,54953,53559],{},[961,54955,54956],{},"Professional Audio Software Developer",[842,54958,52316],{},[1074,54960,54962],{"id":54961},"artlist",[996,54963,54964],{},"Artlist",[842,54966,54967,52297,54971],{},[846,54968,5669],{"href":54969,"rel":54970},"https://www.linkedin.com/company/art-list/",[850],[846,54972,52302],{"href":54973,"rel":54974},"https://www.artlistjobs.io/r&d",[850],[842,54976,54977],{},"Location(s): 🇬🇧 🇮🇱",[958,54979,54980,54983,54986,54988,54991,54994,54996,54999],{},[961,54981,54982],{},"Senior Full Stack Developer",[961,54984,54985],{},"QA Engineer",[961,54987,54026],{},[961,54989,54990],{},"Senior Data Scientist",[961,54992,54993],{},"Full Stack Developer (Backend Oriented)",[961,54995,52659],{},[961,54997,54998],{},"Full Stack Developer (Frontend Oriented)",[961,55000,55001],{},"Infra Data Engineer",[842,55003,52316],{},[1074,55005,55007],{"id":55006},"disco",[996,55008,55009],{},"DISCO",[842,55011,55012,52297,55016],{},[846,55013,5669],{"href":55014,"rel":55015},"https://www.linkedin.com/company/disco-ac/",[850],[846,55017,52302],{"href":55018,"rel":55019},"https://apply.workable.com/disco/#jobs",[850],[842,55021,55022],{},"Location(s): 🇦🇺",[958,55024,55025],{},[961,55026,55027],{},"Senior Frontend Engineer",[842,55029,52316],{},[1074,55031,55033],{"id":55032},"triller",[996,55034,55035],{},"Triller",[842,55037,55038,52297,55042],{},[846,55039,5669],{"href":55040,"rel":55041},"https://www.linkedin.com/company/triller/",[850],[846,55043,52302],{"href":55044,"rel":55045},"https://trillercorp.com/careers/",[850],[842,55047,53794],{},[958,55049,55050],{},[961,55051,55052],{},"Jr. Developer – UI/UX",[842,55054,52316],{},[1074,55056,55057],{"id":53408},[996,55058,53411],{},[842,55060,55061,52297,55064],{},[846,55062,5669],{"href":53416,"rel":55063},[850],[846,55065,52302],{"href":55066,"rel":55067},"https://www.umusiccareers.com/unitedkingdom",[850],[842,55069,55070],{},"Location(s): 🇺🇸 🇬🇧",[958,55072,55073,55076,55079,55082],{},[961,55074,55075],{},"Product Marketing Manager (pro audio)",[961,55077,55078],{},"Senior Cybersecurity Engineer",[961,55080,55081],{},"AI & Audio Scientist",[961,55083,55084],{},"Senior Product Manager",[842,55086,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":55088},[55089,55090,55091,55092,55093,55094,55095,55096,55097,55098,55099,55100,55101,55102,55103,55104,55105,55106,55107,55108,55109,55110,55111,55112,55113,55114,55115,55116,55117,55118,55119,55120,55121,55122,55123,55124,55125,55126,55127,55128,55129,55130,55131,55132],{"id":53752,"depth":1112,"text":53755},{"id":53778,"depth":1112,"text":53781},{"id":53810,"depth":1112,"text":53813},{"id":53832,"depth":1112,"text":53835},{"id":53884,"depth":1112,"text":53887},{"id":53910,"depth":1112,"text":53913},{"id":53939,"depth":1112,"text":53942},{"id":53974,"depth":1112,"text":53977},{"id":54006,"depth":1112,"text":54009},{"id":54031,"depth":1112,"text":54034},{"id":54068,"depth":1112,"text":54071},{"id":54098,"depth":1112,"text":10406},{"id":54123,"depth":1112,"text":54126},{"id":53197,"depth":1112,"text":53200},{"id":54179,"depth":1112,"text":54182},{"id":54205,"depth":1112,"text":54208},{"id":54231,"depth":1112,"text":54234},{"id":54262,"depth":1112,"text":54265},{"id":54291,"depth":1112,"text":54294},{"id":53015,"depth":1112,"text":12942},{"id":54336,"depth":1112,"text":54339},{"id":53134,"depth":1112,"text":53137},{"id":53165,"depth":1112,"text":53168},{"id":54410,"depth":1112,"text":54413},{"id":54465,"depth":1112,"text":54468},{"id":54495,"depth":1112,"text":54498},{"id":54521,"depth":1112,"text":54524},{"id":54550,"depth":1112,"text":54553},{"id":54587,"depth":1112,"text":54590},{"id":54628,"depth":1112,"text":54631},{"id":54663,"depth":1112,"text":54666},{"id":54694,"depth":1112,"text":54697},{"id":54728,"depth":1112,"text":54731},{"id":54753,"depth":1112,"text":54756},{"id":54781,"depth":1112,"text":54784},{"id":54808,"depth":1112,"text":54811},{"id":54842,"depth":1112,"text":54845},{"id":54879,"depth":1112,"text":54882},{"id":53598,"depth":1112,"text":53601},{"id":54938,"depth":1112,"text":54941},{"id":54961,"depth":1112,"text":54964},{"id":55006,"depth":1112,"text":55009},{"id":55032,"depth":1112,"text":55035},{"id":53408,"depth":1112,"text":53411},"2025-04-22T00:00:00.000Z","Curated list of tech job openings in the music industry for April 2025. Discover roles at music startups, labels, and streaming platforms.",{"src":55136},"/images/blog/musictechlab_blog_music-industry-tech-openings-april-2025-update.webp",{},{"title":213,"description":55134},[5678,5523],"XYMQ6pNL1PCzR6AHRuw_aJV0D6ZbSJ1_bMq6w4KArao",{"id":55142,"title":241,"authors":55143,"badge":723,"body":55146,"category":5678,"client":723,"date":56488,"description":56489,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":56490,"keyTakeaways":723,"meta":56492,"navigation":738,"path":242,"seo":56493,"status":723,"stem":243,"tags":56494,"teaser":723,"__hash__":56495},"posts/blog/newsletter/music-industry-tech-openings-march-2025-update.md",[55144],{"name":50093,"to":50094,"avatar":55145},{"src":50096},{"type":725,"value":55147,"toc":56443},[55148,55154,55164,55167,55202,55204,55210,55220,55223,55232,55234,55240,55250,55252,55257,55259,55263,55271,55273,55277,55279,55284,55294,55297,55305,55307,55313,55322,55325,55330,55332,55338,55348,55350,55355,55357,55361,55369,55372,55376,55378,55384,55394,55396,55401,55403,55407,55415,55417,55423,55425,55431,55441,55444,55457,55459,55465,55475,55477,55482,55484,55488,55498,55500,55508,55510,55516,55526,55529,55543,55545,55551,55561,55564,55595,55597,55603,55613,55616,55626,55628,55634,55644,55647,55665,55667,55673,55682,55684,55699,55701,55707,55717,55719,55727,55729,55733,55741,55744,55751,55753,55759,55769,55772,55789,55791,55797,55807,55809,55817,55819,55823,55831,55833,55841,55843,55849,55859,55861,55865,55867,55873,55882,55885,55895,55897,55903,55913,55915,55920,55922,55928,55938,55940,55945,55947,55951,55961,55964,55975,55977,55981,55990,55992,56010,56012,56018,56028,56031,56050,56052,56058,56067,56069,56073,56075,56081,56091,56094,56102,56104,56110,56120,56123,56139,56141,56147,56157,56159,56169,56171,56177,56187,56189,56195,56197,56203,56213,56215,56220,56222,56228,56238,56241,56246,56248,56254,56264,56266,56273,56275,56281,56291,56293,56297,56299,56305,56315,56317,56336,56338,56344,56354,56357,56362,56364,56370,56380,56382,56413,56415,56421,56431,56433,56441],[1074,55149,55151],{"id":55150},"elevenlabs",[996,55152,55153],{},"ElevenLabs",[842,55155,55156,52297,55160,52316],{},[846,55157,5669],{"href":55158,"rel":55159},"https://www.linkedin.com/company/elevenlabsio/",[850],[846,55161,52302],{"href":55162,"rel":55163},"https://elevenlabs.io/careers#jobs",[850],[842,55165,55166],{},"Location(s): 🇺🇸 🇬🇧 🇵🇱 🇩🇪 🇧🇬",[958,55168,55169,55172,55175,55178,55181,55184,55187,55190,55193,55196,55199],{},[961,55170,55171],{},"AI Safety Operations",[961,55173,55174],{},"Engineer (Back-End)",[961,55176,55177],{},"Data Labeler - Music",[961,55179,55180],{},"Design Engineer",[961,55182,55183],{},"Forward Deployed Engineer",[961,55185,55186],{},"Full-Stack Engineer (Front-End Leaning)",[961,55188,55189],{},"Full-Stack Engineer (Back-End Leaning - Core)",[961,55191,55192],{},"Full-Stack Growth Engineer",[961,55194,55195],{},"Website Designer",[961,55197,55198],{},"Machine Learning Researcher",[961,55200,55201],{},"Website Engineer",[842,55203,52316],{},[1074,55205,55207],{"id":55206},"beatstars",[996,55208,55209],{},"BeatStars",[842,55211,55212,52297,55216,52316],{},[846,55213,5669],{"href":55214,"rel":55215},"https://www.linkedin.com/company/beatstars/",[850],[846,55217,52302],{"href":55218,"rel":55219},"https://careers.beatstars.com/#jobs",[850],[842,55221,55222],{},"Location(s): 🇨🇦 🇺🇸 🌍",[958,55224,55225,55227,55229],{},[961,55226,54118],{},[961,55228,54985],{},[961,55230,55231],{},"Senior Backend Java Engineer",[842,55233,52316],{},[1074,55235,55237],{"id":55236},"ebilet-grupa-allegro",[996,55238,55239],{},"eBilet (Grupa Allegro)",[842,55241,55242,52297,55246,52316],{},[846,55243,5669],{"href":55244,"rel":55245},"https://www.linkedin.com/company/ebilet-polska/",[850],[846,55247,52302],{"href":55248,"rel":55249},"https://jobs.allegro.eu/offer/",[850],[842,55251,55222],{},[958,55253,55254],{},[961,55255,55256],{},"Software Engineer 2 (.NET, Angular)",[842,55258,52316],{},[1074,55260,55261],{"id":54336},[996,55262,54339],{},[842,55264,55265,52297,55268,52316],{},[846,55266,5669],{"href":54344,"rel":55267},[850],[846,55269,52302],{"href":54348,"rel":55270},[850],[842,55272,54221],{},[958,55274,55275],{},[961,55276,54356],{},[842,55278,52316],{},[1074,55280,55282],{"id":55281},"deezer",[996,55283,40953],{},[842,55285,55286,52297,55290,52316],{},[846,55287,5669],{"href":55288,"rel":55289},"https://www.linkedin.com/company/deezer/",[850],[846,55291,52302],{"href":55292,"rel":55293},"https://www.deezerjobs.com/en/jobs/",[850],[842,55295,55296],{},"Location(s): 🇫🇷 🇺🇸 🇬🇧",[958,55298,55299,55302],{},[961,55300,55301],{},"Data Analyst Intern m/f/d",[961,55303,55304],{},"Backend Engineer - Catalog",[842,55306,52316],{},[1074,55308,55310],{"id":55309},"siriusxm-pandora",[996,55311,55312],{},"SiriusXM (Pandora)",[842,55314,52316,55315,52297,55318,52316],{},[846,55316,5669],{"href":54418,"rel":55317},[850],[846,55319,52302],{"href":55320,"rel":55321},"https://careers.siriusxm.com/careers/",[850],[842,55323,55324],{},"Location(s): 🇺🇸 🌍",[958,55326,55327],{},[961,55328,55329],{},"Senior Systems Engineer",[842,55331,52316],{},[1074,55333,55335],{"id":55334},"unhurd-music",[996,55336,55337],{},"un:hurd music",[842,55339,52316,55340,52297,55344,52316],{},[846,55341,5669],{"href":55342,"rel":55343},"https://www.linkedin.com/company/unhurdmusic/",[850],[846,55345,52302],{"href":55346,"rel":55347},"https://www.adzuna.co.uk/jobs/details/5112238223",[850],[842,55349,53768],{},[958,55351,55352],{},[961,55353,55354],{},"Tech Lead",[842,55356,52316],{},[1074,55358,55359],{"id":53165},[996,55360,53168],{},[842,55362,55363,52297,55366,52316],{},[846,55364,5669],{"href":53173,"rel":55365},[850],[846,55367,52302],{"href":53177,"rel":55368},[850],[842,55370,55371],{},"Location(s): 🇨🇦 🏢",[958,55373,55374],{},[961,55375,53189],{},[842,55377,52316],{},[1074,55379,55381],{"id":55380},"neural-dsp",[996,55382,55383],{},"Neural DSP",[842,55385,55386,52297,55390,52316],{},[846,55387,5669],{"href":55388,"rel":55389},"https://www.linkedin.com/company/neuraldsp/",[850],[846,55391,52302],{"href":55392,"rel":55393},"https://careers.neuraldsp.com/jobs",[850],[842,55395,54221],{},[958,55397,55398],{},[961,55399,55400],{},"Embedded Engineer Factory Operations",[842,55402,52316],{},[1074,55404,55405],{"id":54521},[996,55406,54524],{},[842,55408,55409,52297,55412,52316],{},[846,55410,5669],{"href":54529,"rel":55411},[850],[846,55413,52302],{"href":54533,"rel":55414},[850],[842,55416,54537],{},[958,55418,55419,55421],{},[961,55420,54542],{},[961,55422,54545],{},[842,55424,52316],{},[1074,55426,55428],{"id":55427},"podimo",[996,55429,55430],{},"Podimo",[842,55432,55433,52297,55437,52316],{},[846,55434,5669],{"href":55435,"rel":55436},"https://www.linkedin.com/company/podimo/",[850],[846,55438,52302],{"href":55439,"rel":55440},"https://careers.podimo.com/#jobs",[850],[842,55442,55443],{},"Location(s): 🇱🇹 🌍 🇩🇰",[958,55445,55446,55449,55452,55454],{},[961,55447,55448],{},"Global Senior Designer (Maternity Cover)",[961,55450,55451],{},"Lead Frontend Engineer",[961,55453,54118],{},[961,55455,55456],{},"Engineering Manager, Pathfinders",[842,55458,52316],{},[1074,55460,55462],{"id":55461},"impulse-audio-lab",[996,55463,55464],{},"Impulse Audio Lab",[842,55466,55467,52297,55471,52316],{},[846,55468,5669],{"href":55469,"rel":55470},"https://www.linkedin.com/company/impulse-audio-lab/",[850],[846,55472,52302],{"href":55473,"rel":55474},"https://impulse-audio-lab.com/",[850],[842,55476,53900],{},[958,55478,55479],{},[961,55480,55481],{},"Audio Software Developer",[842,55483,52316],{},[1074,55485,55486],{"id":52631},[996,55487,52634],{},[842,55489,55490,52297,55494],{},[846,55491,52302],{"href":55492,"rel":55493},"https://sound.com/careers",[850],[846,55495,5669],{"href":55496,"rel":55497},"https://www.linkedin.com/company/sound/posts/",[850],[842,55499,54221],{},[958,55501,55502,55504,55506],{},[961,55503,52653],{},[961,55505,52659],{},[961,55507,52662],{},[842,55509,52316],{},[1074,55511,55513],{"id":55512},"revelator",[996,55514,55515],{},"Revelator",[842,55517,55518,52297,55522,52316],{},[846,55519,5669],{"href":55520,"rel":55521},"https://www.linkedin.com/company/revelator-enterprises/",[850],[846,55523,52302],{"href":55524,"rel":55525},"https://revelator.recruitee.com/",[850],[842,55527,55528],{},"Location(s): 🇭🇺 🇮🇱 🇧🇬",[958,55530,55531,55534,55537,55540],{},[961,55532,55533],{},"API Support Engineer",[961,55535,55536],{},"Senior AI Engineer",[961,55538,55539],{},"Senior Software Architect",[961,55541,55542],{},"Senior .NET Core Developer",[842,55544,52316],{},[1074,55546,55548],{"id":55547},"n-ix",[996,55549,55550],{},"N-IX",[842,55552,55553,52297,55557,52316],{},[846,55554,5669],{"href":55555,"rel":55556},"https://www.linkedin.com/company/n-ix/",[850],[846,55558,52302],{"href":55559,"rel":55560},"https://careers.n-ix.com/jobs/",[850],[842,55562,55563],{},"Location(s): 🇵🇱 🇺🇦",[958,55565,55566,55569,55572,55574,55577,55580,55583,55586,55589,55592],{},[961,55567,55568],{},"Senior HPC Data Engineer",[961,55570,55571],{},"Senior Full-Stack Engineer (React+Java)",[961,55573,52653],{},[961,55575,55576],{},"Middle Java Engineer",[961,55578,55579],{},"Senior Scala Engineer",[961,55581,55582],{},"Senior React Engineer",[961,55584,55585],{},"Senior Java Full Stack Engineer",[961,55587,55588],{},"Senior iOS Engineer",[961,55590,55591],{},"Lead DevOps Engineer",[961,55593,55594],{},"AI Engineer",[842,55596,52316],{},[1074,55598,55600],{"id":55599},"acast",[996,55601,55602],{},"Acast",[842,55604,55605,52297,55609,52316],{},[846,55606,5669],{"href":55607,"rel":55608},"https://www.linkedin.com/products/acast-ab-acast/",[850],[846,55610,52302],{"href":55611,"rel":55612},"https://careers.acast.com/jobs",[850],[842,55614,55615],{},"Location(s): 🇸🇪",[958,55617,55618,55621,55624],{},[961,55619,55620],{},"Senior Data Analyst",[961,55622,55623],{},"Software Engineer (Growth)",[961,55625,52653],{},[842,55627,52316],{},[1074,55629,55631],{"id":55630},"anghami",[996,55632,55633],{},"Anghami",[842,55635,55636,52297,55640,52316],{},[846,55637,5669],{"href":55638,"rel":55639},"https://www.linkedin.com/company/anghami/",[850],[846,55641,52302],{"href":55642,"rel":55643},"https://anghami.breezy.hr/",[850],[842,55645,55646],{},"Location(s): 🇱🇧",[958,55648,55649,55652,55654,55657,55660,55662],{},[961,55650,55651],{},"Android Engineer",[961,55653,52439],{},[961,55655,55656],{},"Frontend Engineer",[961,55658,55659],{},"Senior Backend Software Engineer",[961,55661,52878],{},[961,55663,55664],{},"iOS Engineer",[842,55666,52316],{},[1074,55668,55670],{"id":55669},"avid",[996,55671,55672],{},"Avid",[842,55674,55675,52297,55678,52316],{},[846,55676,5669],{"href":54595,"rel":55677},[850],[846,55679,52302],{"href":55680,"rel":55681},"https://www.avid.com/careers",[850],[842,55683,54603],{},[958,55685,55686,55688,55691,55694,55696],{},[961,55687,54614],{},[961,55689,55690],{},"Technical Support Expert",[961,55692,55693],{},"Technical Support Engineer I - Shared Storage",[961,55695,54623],{},[961,55697,55698],{},"Sibelius Software Developer - Junior position",[842,55700,52316],{},[1074,55702,55704],{"id":55703},"ascap",[996,55705,55706],{},"ASCAP",[842,55708,55709,52297,55713,52316],{},[846,55710,5669],{"href":55711,"rel":55712},"https://www.linkedin.com/company/ascap/",[850],[846,55714,52302],{"href":55715,"rel":55716},"https://www.ascap.com/jobs",[850],[842,55718,53794],{},[958,55720,55721,55724],{},[961,55722,55723],{},"Information Security Analyst",[961,55725,55726],{},"Senior Salesforce Engineer",[842,55728,52316],{},[1074,55730,55731],{"id":52447},[996,55732,52450],{},[842,55734,55735,52297,55738,52316],{},[846,55736,5669],{"href":52455,"rel":55737},[850],[846,55739,52302],{"href":52459,"rel":55740},[850],[842,55742,55743],{},"Location(s): 🇬🇧 🇩🇪",[958,55745,55746,55749],{},[961,55747,55748],{},"FPGA Audio Engineer",[961,55750,52470],{},[842,55752,52316],{},[1074,55754,55756],{"id":55755},"envato",[996,55757,55758],{},"Envato",[842,55760,55761,52297,55765,52316],{},[846,55762,5669],{"href":55763,"rel":55764},"https://www.linkedin.com/company/envato/",[850],[846,55766,52302],{"href":55767,"rel":55768},"https://jobs.lever.co/envato-2",[850],[842,55770,55771],{},"Location(s): 🇦🇺 🇳🇿 🇲🇽",[958,55773,55774,55777,55780,55783,55786],{},[961,55775,55776],{},"Engineering Manager",[961,55778,55779],{},"Lead Infrastructure Engineer",[961,55781,55782],{},"Senior Engineer (Customer Projects & Workflow)",[961,55784,55785],{},"Senior Engineer (Search Platform)",[961,55787,55788],{},"Senior Security Engineer",[842,55790,52316],{},[1074,55792,55794],{"id":55793},"music-vine",[996,55795,55796],{},"Music Vine",[842,55798,55799,52297,55803,52316],{},[846,55800,5669],{"href":55801,"rel":55802},"https://www.linkedin.com/company/music-vine/",[850],[846,55804,52302],{"href":55805,"rel":55806},"https://musicvine.com/careers",[850],[842,55808,53768],{},[958,55810,55811,55814],{},[961,55812,55813],{},"Front End Developer",[961,55815,55816],{},"Senior UI/UX Designer",[842,55818,52316],{},[1074,55820,55821],{"id":54465},[996,55822,54468],{},[842,55824,55825,52297,55828,52316],{},[846,55826,5669],{"href":54473,"rel":55827},[850],[846,55829,52302],{"href":54477,"rel":55830},[850],[842,55832,53794],{},[958,55834,55835,55837,55839],{},[961,55836,52546],{},[961,55838,52653],{},[961,55840,54490],{},[842,55842,52316],{},[1074,55844,55846],{"id":55845},"native-instruments",[996,55847,55848],{},"Native Instruments",[842,55850,55851,52297,55855,52316],{},[846,55852,5669],{"href":55853,"rel":55854},"https://www.linkedin.com/company/native-instruments/",[850],[846,55856,52302],{"href":55857,"rel":55858},"https://www.native-instruments.com/en/career-center/",[850],[842,55860,53900],{},[958,55862,55863],{},[961,55864,52878],{},[842,55866,52316],{},[1074,55868,55870],{"id":55869},"splash",[996,55871,55872],{},"Splash",[842,55874,55875,52297,55878,52316],{},[846,55876,5669],{"href":52483,"rel":55877},[850],[846,55879,52302],{"href":55880,"rel":55881},"https://www.splashmusic.com/work-with-us",[850],[842,55883,55884],{},"Location(s): 🇦🇺 🌍",[958,55886,55887,55890,55892],{},[961,55888,55889],{},"Applied Machine Learning Engineer",[961,55891,52510],{},[961,55893,55894],{},"Technical Project Manager (Scrum Master)",[842,55896,52316],{},[1074,55898,55900],{"id":55899},"musixmatch",[996,55901,55902],{},"Musixmatch",[842,55904,55905,52297,55909,52316],{},[846,55906,5669],{"href":55907,"rel":55908},"https://www.linkedin.com/company/musixmatch/",[850],[846,55910,52302],{"href":55911,"rel":55912},"https://jobs.lever.co/musixmatch",[850],[842,55914,54373],{},[958,55916,55917],{},[961,55918,55919],{},"Senior Product Designer - Musixmatch Pro",[842,55921,52316],{},[1074,55923,55925],{"id":55924},"openplay",[996,55926,55927],{},"OpenPlay",[842,55929,55930,52297,55934,52316],{},[846,55931,5669],{"href":55932,"rel":55933},"https://www.linkedin.com/company/openplay/",[850],[846,55935,52302],{"href":55936,"rel":55937},"https://openplay.co/careers/",[850],[842,55939,54397],{},[958,55941,55942],{},[961,55943,55944],{},"Senior Ruby Engineer",[842,55946,52316],{},[1074,55948,55949],{"id":52353},[996,55950,52356],{},[842,55952,55953,52297,55957,52316],{},[846,55954,5669],{"href":55955,"rel":55956},"https://www.linkedin.com/company/fender/",[850],[846,55958,52302],{"href":55959,"rel":55960},"https://www.fender.com/pages/careers",[850],[842,55962,55963],{},"Location(s): 🇺🇸 🇩🇪 🇨🇦",[958,55965,55966,55969,55971,55973],{},[961,55967,55968],{},"Technical Engineering Lead, Android",[961,55970,52406],{},[961,55972,52409],{},[961,55974,52385],{},[842,55976,52316],{},[1074,55978,55979],{"id":53660},[996,55980,53663],{},[842,55982,55983,52297,55987,52316],{},[846,55984,5669],{"href":55985,"rel":55986},"https://www.linkedin.com/company/inmusic-bra",[850],[846,55988,52302],{"href":53672,"rel":55989},[850],[842,55991,53768],{},[958,55993,55994,55997,55999,56002,56004,56007],{},[961,55995,55996],{},"Electrical Engineer",[961,55998,53682],{},[961,56000,56001],{},"Senior Electrical Engineer",[961,56003,53688],{},[961,56005,56006],{},"Software Tester - Music Technology",[961,56008,56009],{},"Workshop Engineer",[842,56011,52316],{},[1074,56013,56015],{"id":56014},"reverb",[996,56016,56017],{},"Reverb",[842,56019,56020,52297,56024,52316],{},[846,56021,5669],{"href":56022,"rel":56023},"https://www.linkedin.com/company/reverbdotcom/",[850],[846,56025,52302],{"href":56026,"rel":56027},"https://etsy.wd5.myworkdayjobs.com/Reverb_Careers",[850],[842,56029,56030],{},"Location(s): 🇺🇸 🇲🇽",[958,56032,56033,56036,56038,56040,56043,56045,56047],{},[961,56034,56035],{},"Senior Android Engineer",[961,56037,53340],{},[961,56039,55588],{},[961,56041,56042],{},"iOS Engineer II",[961,56044,54118],{},[961,56046,52376],{},[961,56048,56049],{},"Full-stack Software Engineer II, Fraud & Financial Operations",[842,56051,52316],{},[1074,56053,56055],{"id":56054},"eventide",[996,56056,56057],{},"Eventide",[842,56059,56060,52297,56063,52316],{},[846,56061,5669],{"href":54671,"rel":56062},[850],[846,56064,52302],{"href":56065,"rel":56066},"https://www.eventideaudio.com/employment/",[850],[842,56068,53794],{},[958,56070,56071],{},[961,56072,54683],{},[842,56074,52316],{},[1074,56076,56078],{"id":56077},"dad-digital-audio-denmark",[996,56079,56080],{},"DAD (Digital Audio Denmark)",[842,56082,56083,52297,56087,52316],{},[846,56084,5669],{"href":56085,"rel":56086},"https://www.linkedin.com/company/ntp-technology-a-s/",[850],[846,56088,52302],{"href":56089,"rel":56090},"https://digitalaudio.dk/careers/",[850],[842,56092,56093],{},"Location(s): 🇩🇰",[958,56095,56096,56099],{},[961,56097,56098],{},"Senior softwareudvikler til Pro Audio produkter",[961,56100,56101],{},"Hardwareudvikler til Pro Audio produkter",[842,56103,52316],{},[1074,56105,56107],{"id":56106},"logitech",[996,56108,56109],{},"Logitech",[842,56111,56112,52297,56116,52316],{},[846,56113,5669],{"href":56114,"rel":56115},"https://www.linkedin.com/company/logitech/",[850],[846,56117,52302],{"href":56118,"rel":56119},"https://www.logitech.com/en-us/careers",[850],[842,56121,56122],{},"Location(s): 🇮🇳",[958,56124,56125,56127,56130,56133,56136],{},[961,56126,54803],{},[961,56128,56129],{},"Sr. Audio Algorithm Quality Assurance Engineer",[961,56131,56132],{},"Techno System Lead",[961,56134,56135],{},"Principal Audio ML Engineer",[961,56137,56138],{},"Senior Audio ML Engineer",[842,56140,52316],{},[1074,56142,56144],{"id":56143},"allen-heath",[996,56145,56146],{},"Allen & Heath",[842,56148,56149,52297,56153,52316],{},[846,56150,5669],{"href":56151,"rel":56152},"https://www.linkedin.com/company/allen-&-heath-ltd/",[850],[846,56154,52302],{"href":56155,"rel":56156},"https://www.allen-heath.com/about/careers/current-vacancies/",[850],[842,56158,53768],{},[958,56160,56161,56164,56166],{},[961,56162,56163],{},"Digital Electronic Hardware Engineer",[961,56165,54717],{},[961,56167,56168],{},"High Level Software Engineer",[842,56170,52316],{},[1074,56172,56174],{"id":56173},"levellr",[996,56175,56176],{},"Levellr",[842,56178,56179,52297,56183,52316],{},[846,56180,5669],{"href":56181,"rel":56182},"https://www.linkedin.com/company/levellr/",[850],[846,56184,52302],{"href":56185,"rel":56186},"https://jobs.levellr.com/",[850],[842,56188,54221],{},[958,56190,56191,56193],{},[961,56192,52782],{},[961,56194,53340],{},[842,56196,52316],{},[1074,56198,56200],{"id":56199},"ten2-media",[996,56201,56202],{},"TEN2 Media",[842,56204,56205,52297,56209,52316],{},[846,56206,5669],{"href":56207,"rel":56208},"https://www.linkedin.com/company/ten2media/",[850],[846,56210,52302],{"href":56211,"rel":56212},"https://www.ten2.media/jobs",[850],[842,56214,53794],{},[958,56216,56217],{},[961,56218,56219],{},"Full Stack Dev",[842,56221,52316],{},[1074,56223,56225],{"id":56224},"sonosuite",[996,56226,56227],{},"SonoSuite",[842,56229,56230,52297,56234,52316],{},[846,56231,5669],{"href":56232,"rel":56233},"https://www.linkedin.com/company/sonosuite/",[850],[846,56235,52302],{"href":56236,"rel":56237},"https://sonosuite.com/en/join-us/",[850],[842,56239,56240],{},"Location(s): 🇪🇸",[958,56242,56243],{},[961,56244,56245],{},"Sr. Backend Developer",[842,56247,52316],{},[1074,56249,56251],{"id":56250},"udio",[996,56252,56253],{},"Udio",[842,56255,56256,52297,56260,52316],{},[846,56257,5669],{"href":56258,"rel":56259},"https://www.linkedin.com/company/udiomusic/",[850],[846,56261,52302],{"href":56262,"rel":56263},"https://job-boards.greenhouse.io/udio",[850],[842,56265,53794],{},[958,56267,56268,56271],{},[961,56269,56270],{},"Full-Stack Product Engineer",[961,56272,52504],{},[842,56274,52316],{},[1074,56276,56278],{"id":56277},"sofar-sounds",[996,56279,56280],{},"Sofar Sounds",[842,56282,56283,52297,56287,52316],{},[846,56284,5669],{"href":56285,"rel":56286},"https://www.linkedin.com/company/sofarsounds/",[850],[846,56288,52302],{"href":56289,"rel":56290},"https://jobs.lever.co/sofarsounds",[850],[842,56292,53794],{},[958,56294,56295],{},[961,56296,53282],{},[842,56298,52316],{},[1074,56300,56302],{"id":56301},"yoto",[996,56303,56304],{},"Yoto",[842,56306,56307,52297,56311,52316],{},[846,56308,5669],{"href":56309,"rel":56310},"https://www.linkedin.com/company/yotoplay/",[850],[846,56312,52302],{"href":56313,"rel":56314},"https://careers.yotoplay.com/jobs",[850],[842,56316,53768],{},[958,56318,56319,56321,56324,56327,56330,56333],{},[961,56320,55588],{},[961,56322,56323],{},"Product Designer (UX/UI)",[961,56325,56326],{},"Senior Front End Software Engineer",[961,56328,56329],{},"Senior Embedded Software Engineer",[961,56331,56332],{},"Engineering Manager (Embedded Software)",[961,56334,56335],{},"Head of UX/UI",[842,56337,52316],{},[1074,56339,56341],{"id":56340},"serato",[996,56342,56343],{},"Serato",[842,56345,56346,52297,56350,52316],{},[846,56347,5669],{"href":56348,"rel":56349},"https://www.linkedin.com/company/serato-ltd/",[850],[846,56351,52302],{"href":56352,"rel":56353},"https://serato.com/careers#current-openings",[850],[842,56355,56356],{},"Location(s): 🇳🇿",[958,56358,56359],{},[961,56360,56361],{},"Senior Audio Research Engineer",[842,56363,52316],{},[1074,56365,56367],{"id":56366},"believe",[996,56368,56369],{},"Believe",[842,56371,56372,52297,56376,52316],{},[846,56373,5669],{"href":56374,"rel":56375},"https://www.linkedin.com/company/believeglobal/",[850],[846,56377,52302],{"href":56378,"rel":56379},"https://careers.believe.com/en/jobs/",[850],[842,56381,53955],{},[958,56383,56384,56387,56389,56391,56394,56397,56399,56402,56404,56407,56410],{},[961,56385,56386],{},"Data Analyst Intern",[961,56388,53093],{},[961,56390,55776],{},[961,56392,56393],{},"Lead Data Engineer",[961,56395,56396],{},"Engineering Manager - Data",[961,56398,52504],{},[961,56400,56401],{},"Software Engineer Salesforce / Mulesoft",[961,56403,52653],{},[961,56405,56406],{},"Senior QA Engineer",[961,56408,56409],{},"Staff Engineer",[961,56411,56412],{},"Senior Software Engineer - Java/React",[842,56414,52316],{},[1074,56416,56418],{"id":56417},"broadcast-music-inc-bmi",[996,56419,56420],{},"Broadcast Music, Inc. (BMI)",[842,56422,56423,52297,56427,52316],{},[846,56424,5669],{"href":56425,"rel":56426},"https://www.linkedin.com/company/bmi/posts/?feedView=all",[850],[846,56428,52302],{"href":56429,"rel":56430},"https://careers.bmi.com/jobs?jc=30&page_number=2",[850],[842,56432,53794],{},[958,56434,56435,56438],{},[961,56436,56437],{},"Sr. Software Developer",[961,56439,56440],{},"Technical or Sr. Technical Product Owner",[842,56442,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":56444},[56445,56446,56447,56448,56449,56450,56451,56452,56453,56454,56455,56456,56457,56458,56459,56460,56461,56462,56463,56464,56465,56466,56467,56468,56469,56470,56471,56472,56473,56474,56475,56476,56477,56478,56479,56480,56481,56482,56483,56484,56485,56486,56487],{"id":55150,"depth":1112,"text":55153},{"id":55206,"depth":1112,"text":55209},{"id":55236,"depth":1112,"text":55239},{"id":54336,"depth":1112,"text":54339},{"id":55281,"depth":1112,"text":40953},{"id":55309,"depth":1112,"text":55312},{"id":55334,"depth":1112,"text":55337},{"id":53165,"depth":1112,"text":53168},{"id":55380,"depth":1112,"text":55383},{"id":54521,"depth":1112,"text":54524},{"id":55427,"depth":1112,"text":55430},{"id":55461,"depth":1112,"text":55464},{"id":52631,"depth":1112,"text":52634},{"id":55512,"depth":1112,"text":55515},{"id":55547,"depth":1112,"text":55550},{"id":55599,"depth":1112,"text":55602},{"id":55630,"depth":1112,"text":55633},{"id":55669,"depth":1112,"text":55672},{"id":55703,"depth":1112,"text":55706},{"id":52447,"depth":1112,"text":52450},{"id":55755,"depth":1112,"text":55758},{"id":55793,"depth":1112,"text":55796},{"id":54465,"depth":1112,"text":54468},{"id":55845,"depth":1112,"text":55848},{"id":55869,"depth":1112,"text":55872},{"id":55899,"depth":1112,"text":55902},{"id":55924,"depth":1112,"text":55927},{"id":52353,"depth":1112,"text":52356},{"id":53660,"depth":1112,"text":53663},{"id":56014,"depth":1112,"text":56017},{"id":56054,"depth":1112,"text":56057},{"id":56077,"depth":1112,"text":56080},{"id":56106,"depth":1112,"text":56109},{"id":56143,"depth":1112,"text":56146},{"id":56173,"depth":1112,"text":56176},{"id":56199,"depth":1112,"text":56202},{"id":56224,"depth":1112,"text":56227},{"id":56250,"depth":1112,"text":56253},{"id":56277,"depth":1112,"text":56280},{"id":56301,"depth":1112,"text":56304},{"id":56340,"depth":1112,"text":56343},{"id":56366,"depth":1112,"text":56369},{"id":56417,"depth":1112,"text":56420},"2025-03-26T00:00:00.000Z","Curated list of tech job openings in the music industry for March 2025. Discover roles at music startups, labels, and MusicTech companies.",{"src":56491},"/images/blog/musictechlab_blog_music-industry-tech-openings-march-2025-update.webp",{},{"title":241,"description":56489},[5678,5523],"Az5yxLL9GtXpfXOLwkBjq9GyUj7zBNxgazB8od4_78k",{"id":56497,"title":225,"authors":56498,"badge":723,"body":56501,"category":5678,"client":723,"date":58275,"description":58276,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":58277,"keyTakeaways":723,"meta":58279,"navigation":738,"path":226,"seo":58280,"status":723,"stem":227,"tags":58281,"teaser":723,"__hash__":58282},"posts/blog/newsletter/music-industry-tech-openings-february-2025-update.md",[56499],{"name":50093,"to":50094,"avatar":56500},{"src":50096},{"type":725,"value":56502,"toc":58225},[56503,56509,56523,56527,56532,56534,56540,56554,56556,56561,56563,56569,56583,56585,56619,56621,56625,56638,56641,56651,56653,56657,56670,56673,56683,56685,56690,56704,56706,56720,56722,56726,56739,56742,56762,56764,56770,56784,56786,56794,56796,56802,56816,56819,56830,56832,56838,56852,56854,56859,56861,56867,56881,56883,56894,56896,56902,56916,56918,56926,56928,56934,56948,56950,56955,56957,56963,56977,56979,56999,57001,57007,57021,57023,57028,57030,57034,57047,57049,57071,57073,57079,57093,57095,57100,57102,57108,57122,57125,57153,57155,57160,57174,57176,57180,57182,57188,57201,57204,57215,57217,57221,57233,57235,57244,57246,57252,57266,57269,57301,57303,57307,57320,57322,57331,57333,57339,57353,57355,57360,57362,57368,57382,57384,57396,57398,57404,57418,57421,57446,57448,57453,57465,57467,57478,57480,57486,57494,57498,57500,57506,57520,57522,57533,57535,57539,57553,57556,57598,57600,57605,57619,57621,57659,57661,57665,57677,57680,57693,57695,57701,57715,57717,57734,57736,57742,57756,57758,57766,57768,57774,57788,57790,57798,57800,57804,57817,57819,57835,57837,57841,57854,57857,57873,57875,57881,57895,57897,57905,57907,57911,57923,57925,57941,57943,57947,57959,57961,57976,57978,57982,57994,57997,58004,58006,58012,58026,58028,58049,58051,58055,58068,58070,58078,58080,58084,58097,58099,58115,58117,58123,58139,58142,58147,58149,58153,58165,58167,58177,58179,58183,58196,58199],[1074,56504,56506,52316],{"id":56505},"limbo",[996,56507,56508],{},"Limbo",[842,56510,56511,52297,56517],{},[846,56512,56515],{"href":56513,"rel":56514},"https://www.linkedin.com/company/limbomusic/",[850],[996,56516,5669],{},[846,56518,56521],{"href":56519,"rel":56520},"https://limbomusic.com/",[850],[996,56522,52302],{},[842,56524,56525,52685],{},[996,56526,52307],{},[958,56528,56529],{},[961,56530,56531],{},"Full-stack Developer",[842,56533,52316],{},[1074,56535,56537],{"id":56536},"audio-precision",[996,56538,56539],{},"Audio Precision",[842,56541,56542,52297,56548],{},[846,56543,56546],{"href":56544,"rel":56545},"https://www.linkedin.com/company/audio-precision/",[850],[996,56547,5669],{},[846,56549,56552],{"href":56550,"rel":56551},"https://www.ap.com/careers",[850],[996,56553,52302],{},[842,56555,53794],{},[958,56557,56558],{},[961,56559,56560],{},"Principal Engineer/Architect",[842,56562,52316],{},[1074,56564,56566],{"id":56565},"adobe",[996,56567,56568],{},"Adobe",[842,56570,56571,52297,56577],{},[846,56572,56575],{"href":56573,"rel":56574},"https://www.linkedin.com/company/adobe/",[850],[996,56576,5669],{},[846,56578,56581],{"href":56579,"rel":56580},"https://careers.adobe.com/us/en/home",[850],[996,56582,52302],{},[842,56584,53794],{},[958,56586,56587,56590,56593,56596,56598,56601,56604,56607,56610,56613,56616],{},[961,56588,56589],{},"Technical Lead Software Engineer",[961,56591,56592],{},"Staff Data Scientist",[961,56594,56595],{},"Sr. Android Machine Learning Engineer",[961,56597,54026],{},[961,56599,56600],{},"Software Development Engineer",[961,56602,56603],{},"Machine Learning Engineer 5 - Platform Engineer",[961,56605,56606],{},"Senior Software Development Engineer",[961,56608,56609],{},"Software Development Engineer (C++)",[961,56611,56612],{},"Senior FullStack Software Engineer - Commerce",[961,56614,56615],{},"Fullstack Engineer",[961,56617,56618],{},"And more…",[842,56620,52316],{},[1074,56622,56623],{"id":55206},[996,56624,55209],{},[842,56626,56627,52297,56632],{},[846,56628,56630],{"href":55214,"rel":56629},[850],[996,56631,5669],{},[846,56633,56636],{"href":56634,"rel":56635},"https://careers.beatstars.com/",[850],[996,56637,52302],{},[842,56639,56640],{},"Location(s): 🇨🇦 🇺🇸",[958,56642,56643,56645,56648],{},[961,56644,55231],{},[961,56646,56647],{},"Data Product Manager",[961,56649,56650],{},"Data Product Designer",[842,56652,52316],{},[1074,56654,56655],{"id":52554},[996,56656,52557],{},[842,56658,56659,52297,56664],{},[846,56660,56662],{"href":52562,"rel":56661},[850],[996,56663,5669],{},[846,56665,56668],{"href":56666,"rel":56667},"https://beatport.bamboohr.com/careers?source=aWQ9NA==",[850],[996,56669,52302],{},[842,56671,56672],{},"Location(s): 🇵🇱 🇺🇸",[958,56674,56675,56677,56680],{},[961,56676,52583],{},[961,56678,56679],{},"Senior Front-end Software Engineer",[961,56681,56682],{},"Senior Full Stack Software Engineer - PHP",[842,56684,52316],{},[1074,56686,56688],{"id":56687},"goaudio",[996,56689,56687],{},[842,56691,56692,52297,56698],{},[846,56693,56696],{"href":56694,"rel":56695},"https://www.linkedin.com/company/goaudioai/",[850],[996,56697,5669],{},[846,56699,56702],{"href":56700,"rel":56701},"https://www.goaudio.ai/careers",[850],[996,56703,52302],{},[842,56705,53900],{},[958,56707,56708,56711,56714,56717],{},[961,56709,56710],{},"Full-Stack Developer (Frontend Focus)",[961,56712,56713],{},"Full-Stack Software Engineer (Backend Focus)",[961,56715,56716],{},"AI Audio Quality Specialist",[961,56718,56719],{},"AI Solutions Engineer & Full Stack Developer",[842,56721,52316],{},[1074,56723,56724],{"id":53101},[996,56725,53104],{},[842,56727,56728,52297,56733],{},[846,56729,56731],{"href":53109,"rel":56730},[850],[996,56732,5669],{},[846,56734,56737],{"href":56735,"rel":56736},"https://apply.workable.com/orfium/#jobs",[850],[996,56738,52302],{},[842,56740,56741],{},"Location(s): 🇬🇷 🇨🇴 🇱🇰",[958,56743,56744,56747,56750,56753,56755,56758,56760],{},[961,56745,56746],{},"Senior Software Engineer in Test",[961,56748,56749],{},"Staff Data Architect/Engineer",[961,56751,56752],{},"Cloud Engineer",[961,56754,55659],{},[961,56756,56757],{},"Lead Software Engineer in Test",[961,56759,53124],{},[961,56761,53127],{},[842,56763,52316],{},[1074,56765,56767],{"id":56766},"bolero",[996,56768,56769],{},"Bolero",[842,56771,56772,52297,56778],{},[846,56773,56776],{"href":56774,"rel":56775},"https://www.linkedin.com/company/bolero-music/",[850],[996,56777,5669],{},[846,56779,56782],{"href":56780,"rel":56781},"https://boleromusic.notion.site/Work-at-Bolero-dac0179cb059408e853c8c4ed6a4dd5f",[850],[996,56783,52302],{},[842,56785,53955],{},[958,56787,56788,56791],{},[961,56789,56790],{},"Data Scientist - Internship",[961,56792,56793],{},"Full-Stack Engineer - Internship",[842,56795,52316],{},[1074,56797,56799],{"id":56798},"google",[996,56800,56801],{},"Google",[842,56803,56804,52297,56810],{},[846,56805,56808],{"href":56806,"rel":56807},"https://www.linkedin.com/company/google/",[850],[996,56809,5669],{},[846,56811,56814],{"href":56812,"rel":56813},"https://www.google.com/about/careers/applications/jobs/results/116221099686929094-account-executive-midmarket-sales-google-customer-solutions",[850],[996,56815,52302],{},[842,56817,56818],{},"Location(s): 🇮🇳 🇺🇸 🇮🇪",[958,56820,56821,56824,56827],{},[961,56822,56823],{},"Partner Engineer, YouTube Ancillary Rights (English)",[961,56825,56826],{},"Program Manager III, Mobile Engineering, YouTube",[961,56828,56829],{},"Performance Lead, Consumer Experience Solutions, YouTube",[842,56831,52316],{},[1074,56833,56835],{"id":56834},"epr-labs",[996,56836,56837],{},"EPR Labs",[842,56839,56840,52297,56846],{},[846,56841,56844],{"href":56842,"rel":56843},"https://www.linkedin.com/company/epr-labs/jobs/",[850],[996,56845,5669],{},[846,56847,56850],{"href":56848,"rel":56849},"https://www.linkedin.com/jobs/view/4143005338/?refId=g852Q4USAogkzE4AtySIeg%3D%3D&trackingId=g852Q4USAogkzE4AtySIeg%3D%3D",[850],[996,56851,52302],{},[842,56853,53990],{},[958,56855,56856],{},[961,56857,56858],{},"Machine Learning Research Developer",[842,56860,52316],{},[1074,56862,56864],{"id":56863},"apple",[996,56865,56866],{},"Apple",[842,56868,56869,52297,56875],{},[846,56870,56873],{"href":56871,"rel":56872},"https://www.linkedin.com/company/apple/",[850],[996,56874,5669],{},[846,56876,56879],{"href":56877,"rel":56878},"https://www.apple.com/careers/uk/work-at-apple.html",[850],[996,56880,52302],{},[842,56882,53768],{},[958,56884,56885,56888,56891],{},[961,56886,56887],{},"Machine Learning Researcher - Apple Music - Music Intelligence",[961,56889,56890],{},"Editor, Apple Vision Pro",[961,56892,56893],{},"Software/ Data Engineer - Apple Music Data Engineering",[842,56895,52316],{},[1074,56897,56899],{"id":56898},"gema",[996,56900,56901],{},"GEMA",[842,56903,56904,52297,56910],{},[846,56905,56908],{"href":56906,"rel":56907},"https://www.linkedin.com/company/gema/",[850],[996,56909,5669],{},[846,56911,56914],{"href":56912,"rel":56913},"https://www.gema.de/de/die-gema/karriere",[850],[996,56915,52302],{},[842,56917,53900],{},[958,56919,56920,56923],{},[961,56921,56922],{},"Business / Data Analyst",[961,56924,56925],{},"Data Analyst / Community Manager",[842,56927,52316],{},[1074,56929,56931],{"id":56930},"moon-by-simaudio",[996,56932,56933],{},"MOON by Simaudio",[842,56935,56936,52297,56942],{},[846,56937,56940],{"href":56938,"rel":56939},"https://www.linkedin.com/company/simaudio/",[850],[996,56941,5669],{},[846,56943,56946],{"href":56944,"rel":56945},"https://simaudio.com/en/careers/",[850],[996,56947,52302],{},[842,56949,54397],{},[958,56951,56952],{},[961,56953,56954],{},"Machinist",[842,56956,52316],{},[1074,56958,56960],{"id":56959},"splice",[996,56961,56962],{},"Splice",[842,56964,56965,52297,56971],{},[846,56966,56969],{"href":56967,"rel":56968},"https://www.linkedin.com/company/splice-com/",[850],[996,56970,5669],{},[846,56972,56975],{"href":56973,"rel":56974},"https://job-boards.greenhouse.io/splice",[850],[996,56976,52302],{},[842,56978,53794],{},[958,56980,56981,56984,56987,56990,56993,56996],{},[961,56982,56983],{},"Director, Engineering (US Remote)",[961,56985,56986],{},"Senior Software Engineer I",[961,56988,56989],{},"Senior Software Engineer II, ML Core (Remote)",[961,56991,56992],{},"Sr. Data Engineer I (Remote - US)",[961,56994,56995],{},"Senior Machine Learning Engineer - Generative Models (Remote)",[961,56997,56998],{},"Sr. Technical Product Manager, Search & Personalization (Remote - US)",[842,57000,52316],{},[1074,57002,57004],{"id":57003},"cd-baby",[996,57005,57006],{},"CD Baby",[842,57008,57009,52297,57015],{},[846,57010,57013],{"href":57011,"rel":57012},"https://www.linkedin.com/company/cd-baby/",[850],[996,57014,5669],{},[846,57016,57019],{"href":57017,"rel":57018},"https://job-boards.greenhouse.io/cdbabyjobs",[850],[996,57020,52302],{},[842,57022,53794],{},[958,57024,57025],{},[961,57026,57027],{},"Head of Product - Direct to Creator",[842,57029,52316],{},[1074,57031,57032],{"id":53939},[996,57033,53942],{},[842,57035,57036,52297,57041],{},[846,57037,57039],{"href":53947,"rel":57038},[850],[996,57040,5669],{},[846,57042,57045],{"href":57043,"rel":57044},"https://www.welcometothejungle.com/en/companies/arturia/jobs",[850],[996,57046,52302],{},[842,57048,53955],{},[958,57050,57051,57054,57057,57060,57063,57066,57068],{},[961,57052,57053],{},"Junior DSP Engineer, PhD",[961,57055,57056],{},"Research and Development Engineer for Linux Systems",[961,57058,57059],{},"Information Systems and ERP Engineer M/F",[961,57061,57062],{},"Software Internship - Accessibility",[961,57064,57065],{},"Software Internship - Experiment and integrate the CLever Audio Plug-in API (CLAP) to improve our product line",[961,57067,53966],{},[961,57069,57070],{},"Software development engineer & Project manager (M/F)",[842,57072,52316],{},[1074,57074,57076],{"id":57075},"anecoica-studio-ug",[996,57077,57078],{},"Anecoica Studio UG",[842,57080,57081,52297,57087],{},[846,57082,57085],{"href":57083,"rel":57084},"https://www.linkedin.com/posts/marcoaccardi_technical-project-manager-ai-generative-ugcPost-7294382592051535872-kEr7/",[850],[996,57086,5669],{},[846,57088,57091],{"href":57089,"rel":57090},"https://www.anecoica.net/",[850],[996,57092,52302],{},[842,57094,53900],{},[958,57096,57097],{},[961,57098,57099],{},"Technical Project Manager AI Music",[842,57101,52316],{},[1074,57103,57105],{"id":57104},"muse-group",[996,57106,57107],{},"Muse Group",[842,57109,57110,52297,57116],{},[846,57111,57114],{"href":57112,"rel":57113},"https://www.linkedin.com/company/muse/",[850],[996,57115,5669],{},[846,57117,57120],{"href":57118,"rel":57119},"https://www.mu.se/careers",[850],[996,57121,52302],{},[842,57123,57124],{},"Location(s): 🇨🇾 Remote, Worldwide",[958,57126,57127,57130,57132,57134,57136,57138,57141,57144,57147,57150],{},[961,57128,57129],{},"Analytics Engineer",[961,57131,54093],{},[961,57133,53498],{},[961,57135,52782],{},[961,57137,52501],{},[961,57139,57140],{},"Data Analyst (Seo Team)",[961,57142,57143],{},"Backend Developer (+ React Skills)",[961,57145,57146],{},"Data Analyst (B2B sales)",[961,57148,57149],{},"Analytics Lead",[961,57151,57152],{},"IT Support Associate",[842,57154,52316],{},[1074,57156,57158],{"id":57157},"single",[996,57159,17318],{},[842,57161,57162,52297,57168],{},[846,57163,57166],{"href":57164,"rel":57165},"https://www.linkedin.com/company/single/jobs/",[850],[996,57167,5669],{},[846,57169,57172],{"href":57170,"rel":57171},"https://www.linkedin.com/jobs/view/4149266560/?refId=LwlQgdQJQxuf7mEkNNVhAQ%3D%3D&trackingId=LwlQgdQJQxuf7mEkNNVhAQ%3D%3D",[850],[996,57173,52302],{},[842,57175,53794],{},[958,57177,57178],{},[961,57179,52376],{},[842,57181,52316],{},[1074,57183,57185],{"id":57184},"in-music",[996,57186,57187],{},"In Music",[842,57189,57190,52297,57195],{},[846,57191,57193],{"href":53668,"rel":57192},[850],[996,57194,5669],{},[846,57196,57199],{"href":57197,"rel":57198},"https://inmusicbrands.pinpointhq.com/en#js-careers-jobs-block",[850],[996,57200,52302],{},[842,57202,57203],{},"Location(s): 🇳🇿 🇬🇧",[958,57205,57206,57208,57211,57213],{},[961,57207,53682],{},[961,57209,57210],{},"Senior Software Engineer C++",[961,57212,53688],{},[961,57214,56009],{},[842,57216,52316],{},[1074,57218,57219],{"id":53627},[996,57220,53630],{},[842,57222,57223,52297,57228],{},[846,57224,57226],{"href":53635,"rel":57225},[850],[996,57227,5669],{},[846,57229,57231],{"href":53639,"rel":57230},[850],[996,57232,52302],{},[842,57234,54327],{},[958,57236,57237,57239,57242],{},[961,57238,53649],{},[961,57240,57241],{},"Music Operations Intern",[961,57243,53652],{},[842,57245,52316],{},[1074,57247,57249],{"id":57248},"dolby",[996,57250,57251],{},"Dolby",[842,57253,57254,52297,57260],{},[846,57255,57258],{"href":57256,"rel":57257},"https://www.linkedin.com/company/dolby-laboratories/",[850],[996,57259,5669],{},[846,57261,57264],{"href":57262,"rel":57263},"https://jobs.dolby.com/careers",[850],[996,57265,52302],{},[842,57267,57268],{},"Location(s): 🇫🇷 🇨🇳 🇺🇸 🇪🇸 🇬🇧",[958,57270,57271,57274,57277,57280,57283,57286,57289,57292,57295,57298],{},[961,57272,57273],{},"Sr. Engineering Manager, Image Platform",[961,57275,57276],{},"Staff Field Application Engineer, Vision",[961,57278,57279],{},"Staff Engineer, Entertainment",[961,57281,57282],{},"Staff Engineer Dolby Vision PC/Gaming",[961,57284,57285],{},"Senior Field Application Engineer",[961,57287,57288],{},"Intern, Field Application Engineering",[961,57290,57291],{},"Staff Software Engineer - XR & Immersive Content",[961,57293,57294],{},"Senior Software Developer, Entertainment",[961,57296,57297],{},"Technical Community Manager - Content Creation & Services",[961,57299,57300],{},"Sr Software Engineer in Test - Dolby Vision",[842,57302,52316],{},[1074,57304,57305],{"id":53408},[996,57306,53411],{},[842,57308,57309,52297,57314],{},[846,57310,57312],{"href":53416,"rel":57311},[850],[996,57313,5669],{},[846,57315,57318],{"href":57316,"rel":57317},"https://www.umusiccareers.com/gethired?location=&category=",[850],[996,57319,52302],{},[842,57321,53794],{},[958,57323,57324,57326,57329],{},[961,57325,53340],{},[961,57327,57328],{},"Network Engineer",[961,57330,53435],{},[842,57332,52316],{},[1074,57334,57336],{"id":57335},"audio-realities",[996,57337,57338],{},"Audio Realities",[842,57340,57341,52297,57347],{},[846,57342,57345],{"href":57343,"rel":57344},"https://www.linkedin.com/jobs/view/4086018288/",[850],[996,57346,5669],{},[846,57348,57351],{"href":57349,"rel":57350},"https://audiorealities.com/",[850],[996,57352,52302],{},[842,57354,53900],{},[958,57356,57357],{},[961,57358,57359],{},"Lead Software Developer for Real-Time Audio Engineer",[842,57361,52316],{},[1074,57363,57365],{"id":57364},"aria",[996,57366,57367],{},"ARiA",[842,57369,57370,52297,57376],{},[846,57371,57374],{"href":57372,"rel":57373},"https://www.linkedin.com/company/applied-research-in-acoustics-llc/",[850],[996,57375,5669],{},[846,57377,57380],{"href":57378,"rel":57379},"https://www.ariacoustics.com/careers/",[850],[996,57381,52302],{},[842,57383,54221],{},[958,57385,57386,57388,57390,57393],{},[961,57387,52376],{},[961,57389,52504],{},[961,57391,57392],{},"R&D Scientist/Engineer",[961,57394,57395],{},"R&D Intern",[842,57397,52316],{},[1074,57399,57401],{"id":57400},"rivian-and-volkswagen-group-technologies",[996,57402,57403],{},"Rivian and Volkswagen Group Technologies",[842,57405,57406,52297,57412],{},[846,57407,57410],{"href":57408,"rel":57409},"https://www.linkedin.com/company/rivian-and-vw-group-technologies/about/",[850],[996,57411,5669],{},[846,57413,57416],{"href":57414,"rel":57415},"https://rivianvw.tech/#careers",[850],[996,57417,52302],{},[842,57419,57420],{},"Location(s): 🇺🇸 🇨🇴",[958,57422,57423,57426,57429,57432,57435,57438,57441,57444],{},[961,57424,57425],{},"Staff Data Engineer, Audio",[961,57427,57428],{},"Staff Software Engineer, Audio",[961,57430,57431],{},"Staff OBD System Development Engineer",[961,57433,57434],{},"Senior Software Engineer - GenAI",[961,57436,57437],{},"Sr. OTA Cloud DevOps Engineer",[961,57439,57440],{},"Staff AI Research Engineer",[961,57442,57443],{},"Sr. Android Developer, Infotainment",[961,57445,52504],{},[842,57447,52316],{},[1074,57449,57450],{"id":56250},[996,57451,57452],{},"UDIO",[842,57454,57455,52297,57460],{},[846,57456,57458],{"href":56258,"rel":57457},[850],[996,57459,5669],{},[846,57461,57463],{"href":56262,"rel":57462},[850],[996,57464,52302],{},[842,57466,53794],{},[958,57468,57469,57471,57474,57476],{},[961,57470,54118],{},[961,57472,57473],{},"Machine Learning Researcher (Music & Audio)",[961,57475,56270],{},[961,57477,52504],{},[842,57479,52316],{},[1074,57481,57483],{"id":57482},"symphony",[996,57484,57485],{},"Symphony",[842,57487,57488],{},[846,57489,57492],{"href":57490,"rel":57491},"https://www.symphonyos.co/careers",[850],[996,57493,52302],{},[958,57495,57496],{},[961,57497,53340],{},[842,57499,52316],{},[1074,57501,57503],{"id":57502},"roblox",[996,57504,57505],{},"Roblox",[842,57507,57508,52297,57514],{},[846,57509,57512],{"href":57510,"rel":57511},"https://www.linkedin.com/company/roblox/",[850],[996,57513,5669],{},[846,57515,57518],{"href":57516,"rel":57517},"https://careers.roblox.com/",[850],[996,57519,52302],{},[842,57521,53794],{},[958,57523,57524,57527,57530],{},[961,57525,57526],{},"Principal Software Engineer - Audio",[961,57528,57529],{},"Senior Full Stack Engineer - Content Music",[961,57531,57532],{},"Senior Software Engineer - Voice",[842,57534,52316],{},[1074,57536,57537],{"id":52698},[996,57538,52701],{},[842,57540,57541,52297,57547],{},[846,57542,57545],{"href":57543,"rel":57544},"https://www.linkedin.com/company/live-nation/about/",[850],[996,57546,5669],{},[846,57548,57551],{"href":57549,"rel":57550},"https://livenation.wd1.myworkdayjobs.com/en-US/LNExternalSite?jobFamilyGroup=def6fe28d9a210a6e1ddb30d81afbf0e",[850],[996,57552,52302],{},[842,57554,57555],{},"Location(s): 🇬🇧 🇬🇷 🇨🇦 🇺🇸 🇳🇿",[958,57557,57558,57561,57564,57567,57569,57572,57574,57576,57578,57581,57584,57587,57589,57592,57595],{},[961,57559,57560],{},"Audio Engineer - Ace of Spades",[961,57562,57563],{},"Director, Data Engineering",[961,57565,57566],{},"Senior Data Engineer (Databricks Platform)",[961,57568,52504],{},[961,57570,57571],{},"Sound Engineer- PT - Archer Music Hall",[961,57573,52745],{},[961,57575,52736],{},[961,57577,54614],{},[961,57579,57580],{},"Systems Engineer - Sports Platform Engineering Services",[961,57582,57583],{},"Senior Database Engineer",[961,57585,57586],{},"Principal software developer",[961,57588,52748],{},[961,57590,57591],{},"Lead Software Engineer",[961,57593,57594],{},"Director of Data Engineering",[961,57596,57597],{},"Audio Engineer- Belasco",[842,57599,52316],{},[1074,57601,57603],{"id":57602},"suno",[996,57604,26104],{},[842,57606,57607,52297,57613],{},[846,57608,57611],{"href":57609,"rel":57610},"https://www.linkedin.com/company/sunomusic/",[850],[996,57612,5669],{},[846,57614,57617],{"href":57615,"rel":57616},"https://jobs.ashbyhq.com/suno",[850],[996,57618,52302],{},[842,57620,53794],{},[958,57622,57623,57626,57629,57631,57634,57636,57639,57641,57644,57646,57649,57652,57655,57657],{},[961,57624,57625],{},"Senior Data Scientist, Growth",[961,57627,57628],{},"Android Software Engineer",[961,57630,53093],{},[961,57632,57633],{},"iOS Software Engineer",[961,57635,52376],{},[961,57637,57638],{},"Software Engineering Internship - Summer 2025",[961,57640,54490],{},[961,57642,57643],{},"Staff Software Engineer, Android",[961,57645,52626],{},[961,57647,57648],{},"Staff Software Engineer, Infrastructure",[961,57650,57651],{},"Staff Software Engineer, iOS",[961,57653,57654],{},"Machine Learning Infrastructure Engineer",[961,57656,52782],{},[961,57658,55180],{},[842,57660,52316],{},[1074,57662,57663],{"id":52353},[996,57664,52356],{},[842,57666,57667,52297,57672],{},[846,57668,57670],{"href":52361,"rel":57669},[850],[996,57671,5669],{},[846,57673,57675],{"href":55959,"rel":57674},[850],[996,57676,52302],{},[842,57678,57679],{},"Location(s): 🇲🇽 🇺🇸 🇩🇪",[958,57681,57682,57684,57687,57690],{},[961,57683,57129],{},[961,57685,57686],{},"Design Engineer III",[961,57688,57689],{},"Lead Mechanical Engineer",[961,57691,57692],{},"Sr. Software Engineer, UI/UX",[842,57694,52316],{},[1074,57696,57698],{"id":57697},"iheart-media",[996,57699,57700],{},"iHeart Media",[842,57702,57703,52297,57709],{},[846,57704,57707],{"href":57705,"rel":57706},"https://www.linkedin.com/company/iheartmedia/",[850],[996,57708,5669],{},[846,57710,57713],{"href":57711,"rel":57712},"https://iheartmedia.wd5.myworkdayjobs.com/External_iHM/jobs",[850],[996,57714,52302],{},[842,57716,53794],{},[958,57718,57719,57722,57725,57728,57731],{},[961,57720,57721],{},"Software Engineer Streaming Audio",[961,57723,57724],{},"Full Stack Software Engineer",[961,57726,57727],{},"Audio Editor",[961,57729,57730],{},"Sr. Backend Software Engineering Manager",[961,57732,57733],{},"Regional Broadcast Engineer",[842,57735,52316],{},[1074,57737,57739],{"id":57738},"cd-projekt-red",[996,57740,57741],{},"CD Projekt Red",[842,57743,57744,52297,57750],{},[846,57745,57748],{"href":57746,"rel":57747},"https://www.linkedin.com/company/cd-projekt-red/",[850],[996,57749,5669],{},[846,57751,57754],{"href":57752,"rel":57753},"https://www.cdprojektred.com/pl/jobs",[850],[996,57755,52302],{},[842,57757,56672],{},[958,57759,57760,57763],{},[961,57761,57762],{},"Audio Programmer",[961,57764,57765],{},"Lead Sound Designer",[842,57767,52316],{},[1074,57769,57771],{"id":57770},"speechmatics",[996,57772,57773],{},"Speechmatics",[842,57775,57776,52297,57782],{},[846,57777,57780],{"href":57778,"rel":57779},"https://www.linkedin.com/company/speechmatics/",[850],[996,57781,5669],{},[846,57783,57786],{"href":57784,"rel":57785},"https://www.speechmatics.com/company/careers/roles",[850],[996,57787,52302],{},[842,57789,53768],{},[958,57791,57792,57795],{},[961,57793,57794],{},"Junior IT Support Assistant",[961,57796,57797],{},"Director of Machine Learning",[842,57799,52316],{},[1074,57801,57802],{"id":54842},[996,57803,54845],{},[842,57805,57806,52297,57811],{},[846,57807,57809],{"href":54850,"rel":57808},[850],[996,57810,5669],{},[846,57812,57815],{"href":57813,"rel":57814},"https://www.uaudio.com/careers",[850],[996,57816,52302],{},[842,57818,53794],{},[958,57820,57821,57824,57827,57830,57833],{},[961,57822,57823],{},"Engineering - Software",[961,57825,57826],{},"Director Marketing Analytics",[961,57828,57829],{},"Engineering - Hardware",[961,57831,57832],{},"Engineering - Mechanical",[961,57834,54874],{},[842,57836,52316],{},[1074,57838,57839],{"id":54961},[996,57840,54964],{},[842,57842,57843,52297,57848],{},[846,57844,57846],{"href":54969,"rel":57845},[850],[996,57847,5669],{},[846,57849,57852],{"href":57850,"rel":57851},"https://www.artlistjobs.io/categories",[850],[996,57853,52302],{},[842,57855,57856],{},"Location(s): 🇮🇱",[958,57858,57859,57861,57863,57865,57867,57869,57871],{},[961,57860,55001],{},[961,57862,52659],{},[961,57864,53498],{},[961,57866,52662],{},[961,57868,54026],{},[961,57870,54982],{},[961,57872,54118],{},[842,57874,52316],{},[1074,57876,57878],{"id":57877},"soundexchange",[996,57879,57880],{},"SoundExchange",[842,57882,57883,52297,57889],{},[846,57884,57887],{"href":57885,"rel":57886},"https://www.linkedin.com/company/soundexchange/",[850],[996,57888,5669],{},[846,57890,57893],{"href":57891,"rel":57892},"https://www.soundexchange.com/careers/",[850],[996,57894,52302],{},[842,57896,53794],{},[958,57898,57899,57902],{},[961,57900,57901],{},"Senior Software Engineer, Python",[961,57903,57904],{},"Senior Software Engineer, Java",[842,57906,52316],{},[1074,57908,57909],{"id":56301},[996,57910,56304],{},[842,57912,57913,52297,57918],{},[846,57914,57916],{"href":56309,"rel":57915},[850],[996,57917,5669],{},[846,57919,57921],{"href":56313,"rel":57920},[850],[996,57922,52302],{},[842,57924,53768],{},[958,57926,57927,57929,57931,57933,57935,57938],{},[961,57928,56323],{},[961,57930,56326],{},[961,57932,55620],{},[961,57934,56329],{},[961,57936,57937],{},"Engineer Manager (Embedded Software)",[961,57939,57940],{},"Director of Hardware Engineering",[842,57942,52316],{},[1074,57944,57945],{"id":55630},[996,57946,55633],{},[842,57948,57949,52297,57954],{},[846,57950,57952],{"href":55638,"rel":57951},[850],[996,57953,5669],{},[846,57955,57957],{"href":55642,"rel":57956},[850],[996,57958,52302],{},[842,57960,55646],{},[958,57962,57963,57965,57968,57970,57972,57974],{},[961,57964,55651],{},[961,57966,57967],{},"Backend Engineer - Payments and Billing Systems",[961,57969,55656],{},[961,57971,55659],{},[961,57973,52878],{},[961,57975,55664],{},[842,57977,52316],{},[1074,57979,57980],{"id":55427},[996,57981,55430],{},[842,57983,57984,52297,57989],{},[846,57985,57987],{"href":55435,"rel":57986},[850],[996,57988,5669],{},[846,57990,57992],{"href":55439,"rel":57991},[850],[996,57993,52302],{},[842,57995,57996],{},"Location(s): 🇩🇪 🇪🇸 🇬🇧 🇳🇱",[958,57998,57999,58001],{},[961,58000,55451],{},[961,58002,58003],{},"Senior Product Designer, Next-Gen UI & Interactions",[842,58005,52316],{},[1074,58007,58009],{"id":58008},"warner-music-group",[996,58010,58011],{},"Warner Music Group",[842,58013,58014,52297,58020],{},[846,58015,58018],{"href":58016,"rel":58017},"https://www.linkedin.com/company/warner-music-group/",[850],[996,58019,5669],{},[846,58021,58024],{"href":58022,"rel":58023},"https://jobs.lever.co/wmg",[850],[996,58025,52302],{},[842,58027,54139],{},[958,58029,58030,58032,58035,58038,58040,58042,58044,58046],{},[961,58031,54118],{},[961,58033,58034],{},"Software Engineer - Data Platform",[961,58036,58037],{},"Staff Backend Software Engineer - Data Platform",[961,58039,53931],{},[961,58041,56752],{},[961,58043,53093],{},[961,58045,55659],{},[961,58047,58048],{},"Software Support Engineer",[842,58050,52316],{},[1074,58052,58053],{"id":53778},[996,58054,53781],{},[842,58056,58057,52297,58062],{},[846,58058,58060],{"href":53786,"rel":58059},[850],[996,58061,5669],{},[846,58063,58066],{"href":58064,"rel":58065},"https://www.epicgames.com/site/en-US/careers/jobs?page=2",[850],[996,58067,52302],{},[842,58069,56640],{},[958,58071,58072,58075],{},[961,58073,58074],{},"Senior Music Designer",[961,58076,58077],{},"Senior Audio Programmer",[842,58079,52316],{},[1074,58081,58082],{"id":52939},[996,58083,52942],{},[842,58085,58086,52297,58091],{},[846,58087,58089],{"href":52947,"rel":58088},[850],[996,58090,5669],{},[846,58092,58095],{"href":58093,"rel":58094},"https://us242.dayforcehcm.com/CandidatePortal/en-US/octavegroup",[850],[996,58096,52302],{},[842,58098,54397],{},[958,58100,58101,58104,58107,58110,58113],{},[961,58102,58103],{},"Data Engineering Specialist",[961,58105,58106],{},"Senior Machine Learning Specialist",[961,58108,58109],{},"Manager, Backend Development",[961,58111,58112],{},"Senior Android Developer/Staff Android Developer",[961,58114,55084],{},[842,58116,52316],{},[1074,58118,58120],{"id":58119},"audioshake",[996,58121,58122],{},"Audioshake",[842,58124,58125,58127,52297,58133],{},[996,58126,52316],{},[846,58128,58131],{"href":58129,"rel":58130},"https://www.linkedin.com/company/audioshake/posts/?feedView=all",[850],[996,58132,5669],{},[846,58134,58137],{"href":58135,"rel":58136},"https://www.audioshake.ai/audioshake-careers",[850],[996,58138,52302],{},[842,58140,58141],{},"Location(s): 🇺🇸 United States",[958,58143,58144],{},[961,58145,58146],{},"Senior Back End Developer",[842,58148,52316],{},[1074,58150,58151],{"id":53443},[996,58152,53446],{},[842,58154,58155,52297,58160],{},[846,58156,58158],{"href":53451,"rel":58157},[850],[996,58159,5669],{},[846,58161,58163],{"href":53455,"rel":58162},[850],[996,58164,52302],{},[842,58166,55022],{},[958,58168,58169,58172,58174],{},[961,58170,58171],{},"CTO",[961,58173,52504],{},[961,58175,58176],{},"Senior Software Engineer Java",[842,58178,52316],{},[1074,58180,58181],{"id":55150},[996,58182,55153],{},[842,58184,58185,52297,58190],{},[846,58186,58188],{"href":55158,"rel":58187},[850],[996,58189,5669],{},[846,58191,58194],{"href":58192,"rel":58193},"https://jobs.ashbyhq.com/elevenlabs",[850],[996,58195,52302],{},[842,58197,58198],{},"Location(s): 🌍 🇺🇸 🇬🇧 🇵🇱 🇩🇪 🇧🇬 🇵🇹 🇪🇸 🇫🇷 🇮🇳 🇧🇷",[958,58200,58201,58203,58206,58209,58212,58214,58216,58218,58220,58223],{},[961,58202,55177],{},[961,58204,58205],{},"Android Developer",[961,58207,58208],{},"Compliance Engineer",[961,58210,58211],{},"Data Operations",[961,58213,55174],{},[961,58215,55183],{},[961,58217,55189],{},[961,58219,55186],{},[961,58221,58222],{},"Linguist",[961,58224,55198],{},{"title":728,"searchDepth":729,"depth":729,"links":58226},[58227,58229,58230,58231,58232,58233,58234,58235,58236,58237,58238,58239,58240,58241,58242,58243,58244,58245,58246,58247,58248,58249,58250,58251,58252,58253,58254,58255,58256,58257,58258,58259,58260,58261,58262,58263,58264,58265,58266,58267,58268,58269,58270,58271,58272,58273,58274],{"id":56505,"depth":1112,"text":58228},"Limbo‍",{"id":56536,"depth":1112,"text":56539},{"id":56565,"depth":1112,"text":56568},{"id":55206,"depth":1112,"text":55209},{"id":52554,"depth":1112,"text":52557},{"id":56687,"depth":1112,"text":56687},{"id":53101,"depth":1112,"text":53104},{"id":56766,"depth":1112,"text":56769},{"id":56798,"depth":1112,"text":56801},{"id":56834,"depth":1112,"text":56837},{"id":56863,"depth":1112,"text":56866},{"id":56898,"depth":1112,"text":56901},{"id":56930,"depth":1112,"text":56933},{"id":56959,"depth":1112,"text":56962},{"id":57003,"depth":1112,"text":57006},{"id":53939,"depth":1112,"text":53942},{"id":57075,"depth":1112,"text":57078},{"id":57104,"depth":1112,"text":57107},{"id":57157,"depth":1112,"text":17318},{"id":57184,"depth":1112,"text":57187},{"id":53627,"depth":1112,"text":53630},{"id":57248,"depth":1112,"text":57251},{"id":53408,"depth":1112,"text":53411},{"id":57335,"depth":1112,"text":57338},{"id":57364,"depth":1112,"text":57367},{"id":57400,"depth":1112,"text":57403},{"id":56250,"depth":1112,"text":57452},{"id":57482,"depth":1112,"text":57485},{"id":57502,"depth":1112,"text":57505},{"id":52698,"depth":1112,"text":52701},{"id":57602,"depth":1112,"text":26104},{"id":52353,"depth":1112,"text":52356},{"id":57697,"depth":1112,"text":57700},{"id":57738,"depth":1112,"text":57741},{"id":57770,"depth":1112,"text":57773},{"id":54842,"depth":1112,"text":54845},{"id":54961,"depth":1112,"text":54964},{"id":57877,"depth":1112,"text":57880},{"id":56301,"depth":1112,"text":56304},{"id":55630,"depth":1112,"text":55633},{"id":55427,"depth":1112,"text":55430},{"id":58008,"depth":1112,"text":58011},{"id":53778,"depth":1112,"text":53781},{"id":52939,"depth":1112,"text":52942},{"id":58119,"depth":1112,"text":58122},{"id":53443,"depth":1112,"text":53446},{"id":55150,"depth":1112,"text":55153},"2025-02-20T00:00:00.000Z","Curated list of tech job openings in the music industry for February 2025. Browse roles at music startups, labels, and MusicTech companies.",{"src":58278},"/images/blog/musictechlab_blog_music-industry-tech-openings-february-2025-update.webp",{},{"title":225,"description":58276},[5678,5523],"p5fLmdtiVKFYi4nqJZB3CbKWvSjUGi4SQHDg7UvDDQA",{"id":58284,"title":140,"authors":58285,"badge":58288,"body":58289,"category":731,"client":723,"date":58376,"description":58377,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":58378,"keyTakeaways":58380,"meta":58388,"navigation":738,"path":141,"seo":58389,"status":723,"stem":142,"tags":58390,"teaser":723,"__hash__":58391},"posts/blog/music-data/from-startups-to-musictech-maciej-dulski-on-sound-connections-podcast.md",[58286],{"name":834,"to":720,"avatar":58287},{"src":722},{"label":50098,"color":50099},{"type":725,"value":58290,"toc":58371},[58291,58300,58303,58307,58326,58328,58331,58336,58340,58354],[842,58292,58293,58294,58299],{},"Maciej Dulski, co-founder and Partner at MusicTech Lab, sat down with the ",[846,58295,58298],{"href":58296,"rel":58297},"https://www.youtube.com/@SoundConnectionsPodcast",[850],"Sound Connections Podcast"," for a 39-minute conversation about the real challenges of building technology for the music industry.",[842,58301,58302],{},"The episode covers a lot of ground — from how MusicTech Lab got started, to why financing music tech startups is harder than it looks, to where AI fits into music production today.",[863,58304,58306],{"id":58305},"episode-highlights","Episode Highlights",[1045,58308,58310,58314,58318,58322],{"className":58309},[1048,1049,1765,1051,1052],[1054,58311],{"description":58312,"title":58313},"How the team went from a general software house to a company focused entirely on music tech, and what that shift looked like in practice.","The MusicTech Lab Journey",[1054,58315],{"description":58316,"title":58317},"Why music tech startups struggle to raise money compared to other verticals, and what investors often misunderstand about the space.","Financing Challenges",[1054,58319],{"description":58320,"title":58321},"A grounded take on what AI can actually do for artists and producers right now, beyond the hype cycle.","AI in Music Production",[1054,58323],{"description":58324,"title":58325},"How MusicTech Lab works with early-stage founders to turn ideas into working products, and what patterns they see across projects.","Supporting Creativity in Startups",[863,58327,48550],{"id":14447},[842,58329,58330],{},"Music tech sits at a tricky intersection. The technology moves fast, but the industry itself — labels, publishers, collecting societies — often moves slow. Maciej talks openly about navigating that gap: building tools that solve real problems for artists and businesses, without pretending that technology alone fixes everything.",[1572,58332,58333],{},[842,58334,58335],{},"If you work in music tech, plan to build something in the space, or just want an honest look at what it takes — this episode is worth your time.",[863,58337,58339],{"id":58338},"listen-now","Listen Now",[1045,58341,58343,58347,58350],{"className":58342},[13033,50238,50239,1052],[50241,58344],{"color":50243,"label":58345,"target":50245,"to":58346,"variant":50246},"YouTube","https://www.youtube.com/watch?v=W8a2eIXiV7M",[50241,58348],{"color":50249,"label":5070,"target":50245,"to":58349,"variant":50246},"https://open.spotify.com/episode/5JtyJMpr59woMLV1TZL2sa?si=ZLRPSO33TySDQEkxF_DyfQ",[50241,58351],{"color":50249,"label":58352,"target":50245,"to":58353,"variant":50246},"Apple Podcasts","https://podcasts.apple.com/us/podcast/089-expert-maciej-dulski-the-story-behind-musictech-lab/id1703036367?i=1000683274157",[1032,58355,58356],{},[842,58357,58358,58359,58364,58365,58370],{},"This episode is sponsored by ",[846,58360,58363],{"href":58361,"rel":58362},"https://www.allfeat.com/",[850],"Allfeat"," — decentralized blockchain solutions for the music industry. Produced by ",[846,58366,58369],{"href":58367,"rel":58368},"https://amplitude.ventures/",[850],"Amplitude Ventures Consulting"," — partners in early-stage music tech.",{"title":728,"searchDepth":729,"depth":729,"links":58372},[58373,58374,58375],{"id":58305,"depth":729,"text":58306},{"id":14447,"depth":729,"text":48550},{"id":58338,"depth":729,"text":58339},"2025-02-03T00:00:00.000Z","Maciej Dulski from MusicTech Lab discusses the evolving music tech landscape, its impact on artists, and how technology is reshaping the industry.",{"src":58379},"/images/blog/musictechlab_blog_from-startups-to-musictech-maciej-dulski-on-sound-connections-podcast.webp",{"enabled":738,"items":58381},[58382,58384,58386],{"text":58383,"icon":40852},"Maciej Dulski shares how MusicTech Lab shifted from a general software house to music tech.",{"text":58385,"icon":5504},"Music tech startups struggle to raise money compared to other verticals.",{"text":58387,"icon":11614},"AI in music production has real applications today, but the hype outpaces current capabilities.",{},{"title":140,"description":58377},[5523,731],"KFw55yeraH4V2eM4Nno0cLcmt7p5X6kyZYoJ10O21-g",{"id":58393,"title":229,"authors":58394,"badge":723,"body":58397,"category":5678,"client":723,"date":59376,"description":59377,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":59378,"keyTakeaways":723,"meta":59380,"navigation":738,"path":230,"seo":59381,"status":723,"stem":231,"tags":59382,"teaser":723,"__hash__":59383},"posts/blog/newsletter/music-industry-tech-openings-january-2025-update.md",[58395],{"name":50093,"to":50094,"avatar":58396},{"src":50096},{"type":725,"value":58398,"toc":59374},[58399,58413,58417,58429,58433,58446,58466,58479,58487,58501,58505,58517,58523,58534,58542,58554,58568,58578,58586,58598,58602,58614,58640,58651,58656,58669,58677,58691,58695,58708,58716,58729,58744,58754,58762,58776,58787,58798,58818,58831,58845,58858,58866,58879,58886,58900,58905,58916,58928,58939,58944,58957,58961,58975,58981,58991,59000,59014,59018,59030,59046,59060,59066,59077,59086,59096,59101,59111,59119,59129,59134,59147,59152,59165,59180,59192,59211,59221,59246,59260,59282,59295,59306,59319,59324,59335,59341,59354,59359,59370],[842,58400,58401,58404,52297,58408,58412],{},[996,58402,58403],{},"AIMS",[846,58405,5669],{"href":58406,"rel":58407},"https://www.linkedin.com/company/aims-api/",[850],[846,58409,52302],{"href":58410,"rel":58411},"https://www.linkedin.com/company/aims-api/jobs/",[850],"\nLocation(s): 🇺🇸",[958,58414,58415],{},[961,58416,52878],{},[842,58418,58419,58422,52297,58426,58412],{},[996,58420,58421],{},"AudioShake",[846,58423,5669],{"href":58424,"rel":58425},"https://www.linkedin.com/posts/activity-7283324532919083008-cbUw/?utm_source=share&utm_medium=member_android",[850],[846,58427,52302],{"href":58135,"rel":58428},[850],[958,58430,58431],{},[961,58432,58146],{},[842,58434,58435,58438,52297,58442,58412],{},[996,58436,58437],{},"Mozaic.io",[846,58439,5669],{"href":58440,"rel":58441},"https://www.linkedin.com/company/mozaic-io/",[850],[846,58443,52302],{"href":58444,"rel":58445},"https://www.mozaic.io/careers/",[850],[958,58447,58448,58451,58454,58457,58460,58463],{},[961,58449,58450],{},"Sr. .NET Developer",[961,58452,58453],{},"Sr. SDK Developer",[961,58455,58456],{},"Sr. Angular & Chrome Extensions Front End Developer",[961,58458,58459],{},"Sr. Angular Front End Developer",[961,58461,58462],{},"Project Manager",[961,58464,58465],{},"Sr. API Product Owner",[842,58467,58468,58471,52297,58474,58478],{},[996,58469,58470],{},"InMusic",[846,58472,5669],{"href":53668,"rel":58473},[850],[846,58475,52302],{"href":58476,"rel":58477},"https://inmusicbrands.pinpointhq.com/#js-careers-jobs-blo",[850],"\nLocation(s): 🇳🇿 🇬🇧",[958,58480,58481,58483,58485],{},[961,58482,57210],{},[961,58484,53688],{},[961,58486,56009],{},[842,58488,58489,58492,52297,58496,58500],{},[996,58490,58491],{},"AudioStack",[846,58493,5669],{"href":58494,"rel":58495},"https://www.linkedin.com/company/audiostack-ai/",[850],[846,58497,52302],{"href":58498,"rel":58499},"https://apply.workable.com/audiostack/#jobs",[850],"\nLocation(s): 🇬🇧 🇪🇸",[958,58502,58503],{},[961,58504,54026],{},[842,58506,58507,58509,52297,58513,58516],{},[996,58508,56227],{},[846,58510,5669],{"href":58511,"rel":58512},"https://www.linkedin.com/company/sonosuite/?originalSubdomain=es",[850],[846,58514,52302],{"href":56236,"rel":58515},[850],"\nLocation(s): 🇪🇸",[958,58518,58519,58521],{},[961,58520,56245],{},[961,58522,52782],{},[842,58524,58525,58527,52297,58530,58533],{},[996,58526,53446],{},[846,58528,5669],{"href":53451,"rel":58529},[850],[846,58531,52302],{"href":53455,"rel":58532},[850],"\nLocation(s): 🇦🇺",[958,58535,58536,58538,58540],{},[961,58537,53466],{},[961,58539,52504],{},[961,58541,58176],{},[842,58543,58544,58546,52297,58550,58553],{},[996,58545,53601],{},[846,58547,5669],{"href":58548,"rel":58549},"https://www.linkedin.com/posts/palaciosgustavo_embedded-software-engineer-mfd-adam-activity-7284964895811342336-057L/",[850],[846,58551,52302],{"href":53610,"rel":58552},[850],"\nLocation(s): 🇩🇪 🇬🇧",[958,58555,58556,58559,58562,58565],{},[961,58557,58558],{},"Embedded Products System Architect (Staff Engineer)",[961,58560,58561],{},"Embedded Software Engineer (m/f/d) - ADAM Audio GmbH",[961,58563,58564],{},"Embedded Software Engineer - Linea Research",[961,58566,58567],{},"Production Engineer - Martin Audio",[842,58569,58570,58572,52297,58575,58412],{},[996,58571,53479],{},[846,58573,5669],{"href":53484,"rel":58574},[850],[846,58576,52302],{"href":53488,"rel":58577},[850],[958,58579,58580,58582,58584],{},[961,58581,53498],{},[961,58583,53501],{},[961,58585,53504],{},[842,58587,58588,58590,52297,58593,58597],{},[996,58589,55209],{},[846,58591,5669],{"href":55214,"rel":58592},[850],[846,58594,52302],{"href":58595,"rel":58596},"https://careers.beatstars.com/?_gl=1*167xd0j*_gcl_au*MTQ2NjI3NTkwNC4xNzM2OTM0MzAx*_ga*MTgzMTczNDEzMi4xNzM2OTM0MzAx*_ga_EFBBTCG2XY*MTczNjkzNDMwMC4xLjAuMTczNjkzNDMwNS41NS4wLjA.#jobs",[850],"\nLocation(s): 🇨🇦",[958,58599,58600],{},[961,58601,55231],{},[842,58603,58604,58606,52297,58609,58613],{},[996,58605,53835],{},[846,58607,5669],{"href":53840,"rel":58608},[850],[846,58610,52302],{"href":58611,"rel":58612},"https://www.soundhound.com/careers/",[850],"\nLocation(s): 🇮🇳 🇨🇦 🇩🇪 🇺🇸",[958,58615,58616,58619,58622,58624,58627,58629,58632,58635,58638],{},[961,58617,58618],{},"Cognitive Implementation Engineer II",[961,58620,58621],{},"Principal Machine Learning Engineer, Speech Recognition",[961,58623,53879],{},[961,58625,58626],{},"Principal Software Engineer, Wake-up Phrase",[961,58628,53853],{},[961,58630,58631],{},"Senior Machine Learning Engineer, Large Language Models",[961,58633,58634],{},"Senior Software Engineer, Lead (Music Search)",[961,58636,58637],{},"Software Engineer II, Inference Engine (C++, Python)",[961,58639,53873],{},[842,58641,58642,58644,52297,58647,58650],{},[996,58643,53512],{},[846,58645,5669],{"href":53517,"rel":58646},[850],[846,58648,52302],{"href":53521,"rel":58649},[850],"\nLocation(s): 🇨🇭",[958,58652,58653],{},[961,58654,58655],{},"Visual Designer – Web & Apps",[842,58657,58658,58661,52297,58665,58412],{},[996,58659,58660],{},"Sound Ethics",[846,58662,5669],{"href":58663,"rel":58664},"https://www.linkedin.com/company/soundethics/",[850],[846,58666,52302],{"href":58667,"rel":58668},"https://soundethics.notion.site/Careers-At-Sound-Ethics-3dc629e0220643ffaea3241adfae8379",[850],[958,58670,58671,58673,58675],{},[961,58672,52546],{},[961,58674,53093],{},[961,58676,53337],{},[842,58678,58679,58682,52297,58686,58690],{},[996,58680,58681],{},"PRS For Music",[846,58683,5669],{"href":58684,"rel":58685},"https://www.linkedin.com/company/prs-for-music/",[850],[846,58687,52302],{"href":58688,"rel":58689},"https://careers.prsformusic.com/jobs?_gl=1*5myh5b*_ga*MTkyMzY5ODM5MS4xNzM2OTM1ODM5*_ga_VP4GN3G7F0*MTczNjkzNTgzOS4xLjEuMTczNjkzNTg1My40Ni4wLjA.",[850],"\nLocation(s): 🇬🇧",[958,58692,58693],{},[961,58694,52782],{},[842,58696,58697,58700,52297,58704,58412],{},[996,58698,58699],{},"Discogs",[846,58701,5669],{"href":58702,"rel":58703},"https://www.linkedin.com/company/discogs/",[850],[846,58705,52302],{"href":58706,"rel":58707},"https://www.discogs.com/about/careers",[850],[958,58709,58710,58713],{},[961,58711,58712],{},"Senior Full Stack Engineer – Shopping (REMOTE)",[961,58714,58715],{},"Senior Full Stack Engineer – Marketplace – IMS (REMOTE)",[842,58717,58718,58721,52297,58725,58597],{},[996,58719,58720],{},"Musora",[846,58722,5669],{"href":58723,"rel":58724},"https://www.linkedin.com/company/musora/",[850],[846,58726,52302],{"href":58727,"rel":58728},"https://www.musora.com/careers",[850],[958,58730,58731,58734,58737,58739,58741],{},[961,58732,58733],{},"Lead Architect & Back End Engineer",[961,58735,58736],{},"Mobile Engineering Lead",[961,58738,52501],{},[961,58740,53340],{},[961,58742,58743],{},"Senior Back End Engineer",[842,58745,58746,58748,52297,58751,58412],{},[996,58747,53104],{},[846,58749,5669],{"href":53109,"rel":58750},[850],[846,58752,52302],{"href":53113,"rel":58753},[850],[958,58755,58756,58758,58760],{},[961,58757,53124],{},[961,58759,53127],{},[961,58761,52504],{},[842,58763,58764,58767,52297,58771,58775],{},[996,58765,58766],{},"Kobalt Music Group",[846,58768,5669],{"href":58769,"rel":58770},"https://www.linkedin.com/company/kobalt-music-group/",[850],[846,58772,52302],{"href":58773,"rel":58774},"https://www.kobaltmusic.com/careers/",[850],"\nLocation(s): 🇬🇧 🇺🇸",[958,58777,58778,58781,58784],{},[961,58779,58780],{},"IT Support Engineer",[961,58782,58783],{},"Senior Data Platform Engineer",[961,58785,58786],{},"Tech Lead - Data Platform",[842,58788,58789,58791,52297,58794,58797],{},[996,58790,54964],{},[846,58792,5669],{"href":54969,"rel":58793},[850],[846,58795,52302],{"href":57850,"rel":58796},[850],"\nLocation(s): 🇮🇱",[958,58799,58800,58803,58806,58808,58810,58812,58814,58816],{},[961,58801,58802],{},"Senior Business Analyst",[961,58804,58805],{},"Product Analyst",[961,58807,55001],{},[961,58809,54026],{},[961,58811,52659],{},[961,58813,54990],{},[961,58815,54982],{},[961,58817,52662],{},[842,58819,58820,58823,52297,58826,58830],{},[996,58821,58822],{},"BandLab Technologies",[846,58824,5669],{"href":54503,"rel":58825},[850],[846,58827,52302],{"href":58828,"rel":58829},"https://bandlab.pinpointhq.com/",[850],"\nLocation(s): 🇸🇬",[958,58832,58833,58836,58839,58842],{},[961,58834,58835],{},"Backend Engineer, Ads Team",[961,58837,58838],{},"Backend Engineer, Content Moderation and User Protection Team",[961,58840,58841],{},"Backend Engineer, Payments Team",[961,58843,58844],{},"QA Engineer, Studio",[842,58846,58847,58850,52297,58854,58412],{},[996,58848,58849],{},"BeatGig",[846,58851,5669],{"href":58852,"rel":58853},"https://www.linkedin.com/company/beatgig/",[850],[846,58855,52302],{"href":58856,"rel":58857},"https://torpid-receipt-71b.notion.site/Work-at-BeatGig-b9b2647c795b44a386a73ec4517d94e2",[850],[958,58859,58860,58863],{},[961,58861,58862],{},"Senior Software Engineer (Frontend)",[961,58864,58865],{},"Senior Software Engineer (Backend)",[842,58867,58868,58871,52297,58875,58690],{},[996,58869,58870],{},"DICE",[846,58872,5669],{"href":58873,"rel":58874},"https://www.linkedin.com/company/dice-fm/",[850],[846,58876,52302],{"href":58877,"rel":58878},"https://dice.fm/jobs?lng=en#current_openings",[850],[958,58880,58881,58884],{},[961,58882,58883],{},"Technical Program Manager",[961,58885,54118],{},[842,58887,58888,58891,52297,58895,58899],{},[996,58889,58890],{},"Yamaha",[846,58892,5669],{"href":58893,"rel":58894},"https://www.linkedin.com/company/yamaha-corporation/",[850],[846,58896,52302],{"href":58897,"rel":58898},"https://recruiting2.ultipro.com/YAM1001YAMAM/JobBoard/a32d90a2-eea8-4a64-a24c-fe0769d33017/?q=&o=postedDateDesc",[850],"\nLocation(s): 🇯🇵",[958,58901,58902],{},[961,58903,58904],{},"Intern (Acoustics)",[842,58906,58907,58909,52297,58912,58915],{},[996,58908,55633],{},[846,58910,5669],{"href":55638,"rel":58911},[850],[846,58913,52302],{"href":55642,"rel":58914},[850],"\nLocation(s): 🇱🇧 🇦🇪",[958,58917,58918,58920,58922,58924,58926],{},[961,58919,55651],{},[961,58921,57967],{},[961,58923,55656],{},[961,58925,55659],{},[961,58927,55664],{},[842,58929,58930,58932,52297,58935,58938],{},[996,58931,55515],{},[846,58933,5669],{"href":55520,"rel":58934},[850],[846,58936,52302],{"href":55524,"rel":58937},[850],"\nLocation(s): 🇷🇴",[958,58940,58941],{},[961,58942,58943],{},"Senior Front End Angular Developer",[842,58945,58946,58948,52297,58952,58956],{},[996,58947,58491],{},[846,58949,5669],{"href":58950,"rel":58951},"https://www.linkedin.com/products/aflorithmic-labs-apiaudio/",[850],[846,58953,52302],{"href":58954,"rel":58955},"https://apply.workable.com/audiostack/?lng=en#jobs",[850],"\nLocation(s): 🇪🇸 🇬🇧 🇬🇧",[958,58958,58959],{},[961,58960,54026],{},[842,58962,58963,58966,52297,58970,58974],{},[996,58964,58965],{},"MWM",[846,58967,5669],{"href":58968,"rel":58969},"https://www.linkedin.com/company/mwmcompany/",[850],[846,58971,52302],{"href":58972,"rel":58973},"https://mwm.teamtailor.com/jo",[850],"\nLocation(s): 🇫🇷 🇬🇧",[958,58976,58977,58979],{},[961,58978,55588],{},[961,58980,56035],{},[842,58982,58983,58985,52297,58988,58412],{},[996,58984,54468],{},[846,58986,5669],{"href":54473,"rel":58987},[850],[846,58989,52302],{"href":54477,"rel":58990},[850],[958,58992,58993,58995,58998],{},[961,58994,52653],{},[961,58996,58997],{},"Senior Machine Learning Engineer",[961,58999,54490],{},[842,59001,59002,59005,52297,59009,59013],{},[996,59003,59004],{},"Pex",[846,59006,5669],{"href":59007,"rel":59008},"https://www.linkedin.com/company/pexeso/",[850],[846,59010,52302],{"href":59011,"rel":59012},"https://pex.com/careers/",[850],"\nLocation(s): 🇪🇺",[958,59015,59016],{},[961,59017,55620],{},[842,59019,59020,59022,52297,59025,59029],{},[996,59021,57251],{},[846,59023,5669],{"href":57256,"rel":59024},[850],[846,59026,52302],{"href":59027,"rel":59028},"https://jobs.dolby.com/careers?skill=Python&skill=C%2B%2B&skill=Machine%20Learning&skill=Algorithms&skill=Deep%20Learning&skill=Computer%20Science&skill=Pandas&pid=22201721&domain=dolby.com&sort_by=relevance",[850],"\nLocation(s): 🇫🇷 🇺🇸 🇨🇳",[958,59031,59032,59034,59036,59039,59041,59044],{},[961,59033,57294],{},[961,59035,57279],{},[961,59037,59038],{},"Associate Solutions Engineer",[961,59040,57276],{},[961,59042,59043],{},"Senior Field Application Engineer, Auto",[961,59045,57288],{},[842,59047,59048,59051,52297,59055,59059],{},[996,59049,59050],{},"TicketSwap",[846,59052,5669],{"href":59053,"rel":59054},"https://www.linkedin.com/company/ticketswap/",[850],[846,59056,52302],{"href":59057,"rel":59058},"https://jobs.ticketswap.com/?_gl=1*1h0e0ee*_gcl_au*MjA3OTIzNTY5OS4xNzM3MTI2NTQ0",[850],"\nLocation(s): 🇳🇱",[958,59061,59062,59064],{},[961,59063,54614],{},[961,59065,58205],{},[842,59067,59068,59071,52297,59074,58516],{},[996,59069,59070],{},"My Sheet Music Transcription",[846,59072,5669],{"href":52675,"rel":59073},[850],[846,59075,52302],{"href":52679,"rel":59076},[850],[958,59078,59079,59081,59083],{},[961,59080,57724],{},[961,59082,52693],{},[961,59084,59085],{},"Music Editor",[842,59087,59088,59091,59095],{},[996,59089,59090],{},"Ticketr",[846,59092,5669],{"href":59093,"rel":59094},"https://www.linkedin.com/company/ticketr/",[850],"\nLocation(s): 🇫🇷",[958,59097,59098],{},[961,59099,59100],{},"Lead Developer / Web Fullstack",[842,59102,59103,59105,52297,59108,59059],{},[996,59104,53630],{},[846,59106,5669],{"href":53635,"rel":59107},[850],[846,59109,52302],{"href":53639,"rel":59110},[850],[958,59112,59113,59115,59117],{},[961,59114,53649],{},[961,59116,53652],{},[961,59118,57241],{},[842,59120,59121,59123,52297,59126,59095],{},[996,59122,56369],{},[846,59124,52302],{"href":56378,"rel":59125},[850],[846,59127,5669],{"href":56374,"rel":59128},[850],[958,59130,59131],{},[961,59132,59133],{},"Senior Data Engineer (H/F)",[842,59135,59136,59139,52297,59143,58516],{},[996,59137,59138],{},"Voicemod",[846,59140,5669],{"href":59141,"rel":59142},"https://www.linkedin.com/company/voicemod/",[850],[846,59144,52302],{"href":59145,"rel":59146},"https://voicemod.breezy.hr/",[850],[958,59148,59149],{},[961,59150,59151],{},"Data Engineering",[842,59153,59154,59157,52297,59161,58597],{},[996,59155,59156],{},"LyricFind",[846,59158,5669],{"href":59159,"rel":59160},"https://www.linkedin.com/company/lyricfind-inc-/",[850],[846,59162,52302],{"href":59163,"rel":59164},"https://www.lyricfind.com/company/careers",[850],[958,59166,59167,59170,59173,59175,59178],{},[961,59168,59169],{},"Lyric Analyst (Spanish)",[961,59171,59172],{},"Lyric Analyst (Italian)",[961,59174,52745],{},[961,59176,59177],{},"Technical Operations Analyst",[961,59179,52348],{},[842,59181,59182,59184,52297,59187,59191],{},[996,59183,55153],{},[846,59185,5669],{"href":55158,"rel":59186},[850],[846,59188,52302],{"href":59189,"rel":59190},"https://jobs.ashbyhq.com/elevenlabs?departmentId=d8319477-230e-490b-beb9-ca32234f587d",[850],"\nLocation(s): 🇩🇪 🇺🇸 🇵🇱 🇧🇬",[958,59193,59194,59197,59199,59201,59203,59205,59207,59209],{},[961,59195,59196],{},"AI Safety Engineer (Back-End)",[961,59198,58205],{},[961,59200,58208],{},[961,59202,55174],{},[961,59204,55183],{},[961,59206,55189],{},[961,59208,55186],{},[961,59210,55198],{},[842,59212,59213,59215,52297,59218,59095],{},[996,59214,40953],{},[846,59216,5669],{"href":55288,"rel":59217},[850],[846,59219,52302],{"href":55292,"rel":59220},[850],[958,59222,59223,59225,59228,59231,59234,59237,59240,59243],{},[961,59224,55301],{},[961,59226,59227],{},"Backend Engineer - Catalog m/f/d",[961,59229,59230],{},"Lead Backend Engineer – Deezer for Creators (m/f/d)",[961,59232,59233],{},"Frontend Engineer Apprentice - Core Product Listeners",[961,59235,59236],{},"Expert Frontend Engineer - Core Product Listeners m/f/d",[961,59238,59239],{},"Senior Android Engineer m/f/d",[961,59241,59242],{},"Data Engineer m/f/d",[961,59244,59245],{},"Senior Product Manager – Customer & Monetization Revenue Growth (m/f/d)",[842,59247,59248,59251,52297,59255,59259],{},[996,59249,59250],{},"Epidemic Sound",[846,59252,5669],{"href":59253,"rel":59254},"https://www.linkedin.com/company/epidemic-sound/",[850],[846,59256,52302],{"href":59257,"rel":59258},"https://careers.epidemicsound.com/job-openings/",[850],"\nLocation(s): 🇸🇪",[958,59261,59262,59265,59268,59271,59274,59277,59279],{},[961,59263,59264],{},"Engineering Manager - Site Reliability Engineering",[961,59266,59267],{},"Senior Site Reliability Engineer",[961,59269,59270],{},"Senior Backend Engineer - DRM",[961,59272,59273],{},"Senior Growth Engineer",[961,59275,59276],{},"Senior Frontend Engineer - Growth",[961,59278,58997],{},[961,59280,59281],{},"Senior Backend Engineer - Growth",[842,59283,59284,59287,52297,59291,59259],{},[996,59285,59286],{},"Soundtrack Your Band",[846,59288,5669],{"href":59289,"rel":59290},"https://www.linkedin.com/company/soundtrackio/",[850],[846,59292,52302],{"href":59293,"rel":59294},"https://careers.soundtrackyourbrand.com/#jobs",[850],[958,59296,59297,59300,59303],{},[961,59298,59299],{},"Full-stack Engineer (Music Experience)",[961,59301,59302],{},"Senior Frontend Engineer (Music Experience)",[961,59304,59305],{},"Software Engineer for Revenue Engineering",[842,59307,59308,59311,52297,59315,58690],{},[996,59309,59310],{},"ALLOY\n‍",[846,59312,5669],{"href":59313,"rel":59314},"https://www.linkedin.com/company/alloy-sync-distro/posts/?feedView=all",[850],[846,59316,52302],{"href":59317,"rel":59318},"https://www.linkedin.com/posts/tom-stingemore_attn-music-x-data-wizards-we-are-hiring-activity-7283429341819023360-YffD/?utm_source=share&utm_medium=member_desktop",[850],[958,59320,59321],{},[961,59322,59323],{},"Music x Data Wizard",[842,59325,59326,59328,52297,59331,59334],{},[996,59327,53540],{},[846,59329,5669],{"href":53545,"rel":59330},[850],[846,59332,52302],{"href":53549,"rel":59333},[850],"\nLocation(s): 🇩🇪",[958,59336,59337,59339],{},[961,59338,53559],{},[961,59340,55656],{},[842,59342,59343,59346,52297,59350,58412],{},[996,59344,59345],{},"Kits.AI",[846,59347,5669],{"href":59348,"rel":59349},"https://www.linkedin.com/company/kitsai/",[850],[846,59351,52302],{"href":59352,"rel":59353},"https://www.kits.ai/research/internship",[850],[958,59355,59356],{},[961,59357,59358],{},"Research Internship Program (2025)",[842,59360,59361,59363,52297,59366,59334],{},[996,59362,54882],{},[846,59364,5669],{"href":54887,"rel":59365},[850],[846,59367,52302],{"href":59368,"rel":59369},"https://www.bmg.com/de/career/Senior-Data-Analyst.html",[850],[958,59371,59372],{},[961,59373,55620],{},{"title":728,"searchDepth":729,"depth":729,"links":59375},[],"2025-01-23T00:00:00.000Z","Curated list of tech job openings in the music industry for January 2025. Explore roles at music startups, labels, and MusicTech companies.",{"src":59379},"/images/blog/musictechlab_blog_music-industry-tech-openings-january-2025-update.webp",{},{"title":229,"description":59377},[5678,5523],"bMMcB4-teTonJiC6UIw-qghEjCPm0metS-vzxElDAVo",{"id":59385,"title":148,"authors":59386,"badge":723,"body":59389,"category":731,"client":723,"date":63937,"description":63938,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":63939,"keyTakeaways":63941,"meta":63949,"navigation":738,"path":149,"seo":63950,"status":723,"stem":150,"tags":63951,"teaser":723,"__hash__":63952},"posts/blog/music-data/how-to-use-epidemic-sound-mcp-with-claude.md",[59387],{"name":12840,"to":12838,"avatar":59388},{"src":40862},{"type":725,"value":59390,"toc":63895},[59391,59393,59396,59402,59405,59407,59411,59414,59418,59444,59446,59450,59453,59467,59471,59477,59479,59481,59483,59518,59525,59527,59531,59535,59538,59563,59566,59570,59575,59586,59591,59627,59631,59636,59663,59668,59671,59696,59699,59847,59851,59854,59868,59872,59912,59915,59919,59922,59937,59939,59943,59948,59952,59955,59961,59965,59971,59975,59981,59983,59987,59991,59994,60706,60710,62482,62486,63626,63628,63632,63636,63639,63645,63649,63652,63658,63662,63665,63671,63675,63678,63684,63686,63690,63693,63737,63746,63748,63752,63756,63790,63794,63805,63809,63820,63822,63824,63827,63852,63855,63857,63859,63892],[863,59392,27940],{"id":27939},[842,59394,59395],{},"Finding the perfect music for your project has always been a time-consuming task. Whether you're developing a mobile game, creating video content, or building an interactive experience, searching through thousands of tracks can take hours.",[842,59397,59398,59401],{},[996,59399,59400],{},"Enter the Epidemic Sound MCP Server"," - a game-changing integration that brings AI-powered music discovery directly into your development workflow. Combined with Claude (Anthropic's AI assistant), you can now search, discover, and curate music using natural language prompts without ever leaving your coding environment.",[842,59403,59404],{},"In this guide, we'll walk you through everything you need to know to set up and use this powerful combination.",[4937,59406],{},[863,59408,59410],{"id":59409},"what-is-mcp-model-context-protocol","What is MCP (Model Context Protocol)?",[842,59412,59413],{},"MCP (Model Context Protocol) is an open standard developed by Anthropic that allows AI assistants like Claude to connect with external tools and services. Think of it as a bridge that lets Claude interact with real-world APIs and databases.",[1074,59415,59417],{"id":59416},"key-benefits-of-mcp","Key Benefits of MCP:",[958,59419,59420,59426,59432,59438],{},[961,59421,59422,59425],{},[996,59423,59424],{},"Seamless Integration"," - Connect external services directly to your AI workflow",[961,59427,59428,59431],{},[996,59429,59430],{},"Natural Language Interface"," - No need to learn complex APIs",[961,59433,59434,59437],{},[996,59435,59436],{},"Context-Aware"," - AI understands your project and makes relevant suggestions",[961,59439,59440,59443],{},[996,59441,59442],{},"Real-Time Access"," - Get live data from connected services",[4937,59445],{},[863,59447,59449],{"id":59448},"what-is-the-epidemic-sound-mcp-server","What is the Epidemic Sound MCP Server?",[842,59451,59452],{},"Epidemic Sound's MCP Server is their implementation of the Model Context Protocol, designed specifically for music discovery. Instead of manually browsing their catalog of 50,000+ tracks, you can now:",[958,59454,59455,59458,59461,59464],{},[961,59456,59457],{},"Search for music using natural language descriptions",[961,59459,59460],{},"Filter by mood, genre, tempo, and instrumentation",[961,59462,59463],{},"Get curated suggestions based on your project context",[961,59465,59466],{},"Find sound effects from their 200,000+ library",[1074,59468,59470],{"id":59469},"example-prompts-you-can-use","Example Prompts You Can Use:",[1013,59472,59475],{"className":59473,"code":59474,"language":1018},[1016],"\"Find upbeat electronic tracks for a mobile puzzle game\"\n\"Search for calm, ambient music with piano, around 80 BPM\"\n\"I need tense orchestral music for a boss battle scene\"\n\"Find short 10-second loops suitable for UI interactions\"\n",[895,59476,59474],{"__ignoreMap":728},[4937,59478],{},[863,59480,15348],{"id":15347},[842,59482,41016],{},[991,59484,59485,59495,59508],{},[961,59486,59487,59490,59491],{},[996,59488,59489],{},"Node.js"," (LTS version) - ",[846,59492,41029],{"href":59493,"rel":59494},"https://nodejs.org/",[850],[961,59496,59497,28366,59500,41024,59503],{},[996,59498,59499],{},"Claude Desktop",[996,59501,59502],{},"Claude Code CLI",[846,59504,59507],{"href":59505,"rel":59506},"https://claude.ai/download",[850],"Get Claude",[961,59509,59510,41024,59513],{},[996,59511,59512],{},"Epidemic Sound Account",[846,59514,59517],{"href":59515,"rel":59516},"https://www.epidemicsound.com/",[850],"Sign up here",[41054,59519,59520],{},[842,59521,59522,59524],{},[996,59523,41250],{},": MCP servers require a local environment. They do not work in web browsers or mobile apps.",[4937,59526],{},[863,59528,59530],{"id":59529},"step-by-step-setup-guide","Step-by-Step Setup Guide",[1074,59532,59534],{"id":59533},"step-1-install-nodejs","Step 1: Install Node.js",[842,59536,59537],{},"If you don't have Node.js installed, download and install the LTS version:",[1013,59539,59541],{"className":1080,"code":59540,"language":1082,"meta":728,"style":728},"# Verify installation\nnode --version\nnpm --version\n",[895,59542,59543,59548,59556],{"__ignoreMap":728},[1086,59544,59545],{"class":1088,"line":1089},[1086,59546,59547],{"class":1427},"# Verify installation\n",[1086,59549,59550,59553],{"class":1088,"line":729},[1086,59551,59552],{"class":1092},"node",[1086,59554,59555],{"class":1096}," --version\n",[1086,59557,59558,59561],{"class":1088,"line":1112},[1086,59559,59560],{"class":1092},"npm",[1086,59562,59555],{"class":1096},[842,59564,59565],{},"You should see version numbers for both commands.",[1074,59567,59569],{"id":59568},"step-2-install-claude-desktop-or-claude-code","Step 2: Install Claude Desktop or Claude Code",[842,59571,59572],{},[996,59573,59574],{},"Option A: Claude Desktop (GUI)",[958,59576,59577,59583],{},[961,59578,28033,59579],{},[846,59580,59582],{"href":59505,"rel":59581},[850],"claude.ai/download",[961,59584,59585],{},"Install and sign in with your Anthropic account",[842,59587,59588],{},[996,59589,59590],{},"Option B: Claude Code (CLI)",[1013,59592,59594],{"className":1080,"code":59593,"language":1082,"meta":728,"style":728},"# Install Claude Code globally\nnpm install -g @anthropic-ai/claude-code\n\n# Verify installation\nclaude --version\n",[895,59595,59596,59601,59613,59617,59621],{"__ignoreMap":728},[1086,59597,59598],{"class":1088,"line":1089},[1086,59599,59600],{"class":1427},"# Install Claude Code globally\n",[1086,59602,59603,59605,59607,59610],{"class":1088,"line":729},[1086,59604,59560],{"class":1092},[1086,59606,27993],{"class":1096},[1086,59608,59609],{"class":1096}," -g",[1086,59611,59612],{"class":1096}," @anthropic-ai/claude-code\n",[1086,59614,59615],{"class":1088,"line":1112},[1086,59616,3390],{"emptyLinePlaceholder":738},[1086,59618,59619],{"class":1088,"line":1181},[1086,59620,59547],{"class":1427},[1086,59622,59623,59625],{"class":1088,"line":1205},[1086,59624,6485],{"class":1092},[1086,59626,59555],{"class":1096},[1074,59628,59630],{"id":59629},"step-3-add-the-epidemic-sound-mcp-server","Step 3: Add the Epidemic Sound MCP Server",[842,59632,59633],{},[996,59634,59635],{},"Using Claude Code CLI:",[1013,59637,59639],{"className":1080,"code":59638,"language":1082,"meta":728,"style":728},"# Add the Epidemic Sound MCP server with user scope\nclaude mcp add epidemicsound --scope user\n",[895,59640,59641,59646],{"__ignoreMap":728},[1086,59642,59643],{"class":1088,"line":1089},[1086,59644,59645],{"class":1427},"# Add the Epidemic Sound MCP server with user scope\n",[1086,59647,59648,59650,59652,59654,59657,59660],{"class":1088,"line":729},[1086,59649,6485],{"class":1092},[1086,59651,8553],{"class":1096},[1086,59653,8556],{"class":1096},[1086,59655,59656],{"class":1096}," epidemicsound",[1086,59658,59659],{"class":1096}," --scope",[1086,59661,59662],{"class":1096}," user\n",[842,59664,59665],{},[996,59666,59667],{},"Or manually edit the configuration file:",[842,59669,59670],{},"Find your config file:",[958,59672,59673,59681,59688],{},[961,59674,59675,27851,59678],{},[996,59676,59677],{},"macOS",[895,59679,59680],{},"~/Library/Application Support/Claude/claude_desktop_config.json",[961,59682,59683,27851,59685],{},[996,59684,28030],{},[895,59686,59687],{},"%APPDATA%\\Claude\\claude_desktop_config.json",[961,59689,59690,27851,59693],{},[996,59691,59692],{},"Linux",[895,59694,59695],{},"~/.config/Claude/claude_desktop_config.json",[842,59697,59698],{},"Add the following configuration:",[1013,59700,59702],{"className":1136,"code":59701,"language":1139,"meta":728,"style":728},"{\n  \"mcpServers\": {\n    \"epidemicsound\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@anthropic/mcp-epidemicsound\"],\n      \"env\": {\n        \"EPIDEMIC_SOUND_CLIENT_ID\": \"your-client-id\",\n        \"EPIDEMIC_SOUND_CLIENT_SECRET\": \"your-client-secret\"\n      }\n    }\n  }\n}\n",[895,59703,59704,59708,59720,59733,59752,59781,59793,59813,59831,59835,59839,59843],{"__ignoreMap":728},[1086,59705,59706],{"class":1088,"line":1089},[1086,59707,1147],{"class":1146},[1086,59709,59710,59712,59714,59716,59718],{"class":1088,"line":729},[1086,59711,1152],{"class":1146},[1086,59713,1156],{"class":1155},[1086,59715,1159],{"class":1146},[1086,59717,1133],{"class":1146},[1086,59719,1164],{"class":1146},[1086,59721,59722,59724,59727,59729,59731],{"class":1088,"line":1112},[1086,59723,1169],{"class":1146},[1086,59725,59726],{"class":1092},"epidemicsound",[1086,59728,1159],{"class":1146},[1086,59730,1133],{"class":1146},[1086,59732,1164],{"class":1146},[1086,59734,59735,59737,59739,59741,59743,59745,59748,59750],{"class":1088,"line":1181},[1086,59736,1184],{"class":1146},[1086,59738,1188],{"class":1187},[1086,59740,1159],{"class":1146},[1086,59742,1133],{"class":1146},[1086,59744,1195],{"class":1146},[1086,59746,59747],{"class":1096},"npx",[1086,59749,1159],{"class":1146},[1086,59751,1202],{"class":1146},[1086,59753,59754,59756,59758,59760,59762,59764,59766,59768,59770,59772,59774,59777,59779],{"class":1088,"line":1205},[1086,59755,1184],{"class":1146},[1086,59757,1210],{"class":1187},[1086,59759,1159],{"class":1146},[1086,59761,1133],{"class":1146},[1086,59763,1217],{"class":1146},[1086,59765,1159],{"class":1146},[1086,59767,28719],{"class":1096},[1086,59769,1159],{"class":1146},[1086,59771,1227],{"class":1146},[1086,59773,1195],{"class":1146},[1086,59775,59776],{"class":1096},"@anthropic/mcp-epidemicsound",[1086,59778,1159],{"class":1146},[1086,59780,9297],{"class":1146},[1086,59782,59783,59785,59787,59789,59791],{"class":1088,"line":1276},[1086,59784,1184],{"class":1146},[1086,59786,12112],{"class":1187},[1086,59788,1159],{"class":1146},[1086,59790,1133],{"class":1146},[1086,59792,1164],{"class":1146},[1086,59794,59795,59797,59800,59802,59804,59806,59809,59811],{"class":1088,"line":1282},[1086,59796,6046],{"class":1146},[1086,59798,59799],{"class":4109},"EPIDEMIC_SOUND_CLIENT_ID",[1086,59801,1159],{"class":1146},[1086,59803,1133],{"class":1146},[1086,59805,1195],{"class":1146},[1086,59807,59808],{"class":1096},"your-client-id",[1086,59810,1159],{"class":1146},[1086,59812,1202],{"class":1146},[1086,59814,59815,59817,59820,59822,59824,59826,59829],{"class":1088,"line":1288},[1086,59816,6046],{"class":1146},[1086,59818,59819],{"class":4109},"EPIDEMIC_SOUND_CLIENT_SECRET",[1086,59821,1159],{"class":1146},[1086,59823,1133],{"class":1146},[1086,59825,1195],{"class":1146},[1086,59827,59828],{"class":1096},"your-client-secret",[1086,59830,4441],{"class":1146},[1086,59832,59833],{"class":1088,"line":2685},[1086,59834,26783],{"class":1146},[1086,59836,59837],{"class":1088,"line":2700},[1086,59838,1279],{"class":1146},[1086,59840,59841],{"class":1088,"line":3398},[1086,59842,1285],{"class":1146},[1086,59844,59845],{"class":1088,"line":1715},[1086,59846,1291],{"class":1146},[1074,59848,59850],{"id":59849},"step-4-authenticate-with-epidemic-sound","Step 4: Authenticate with Epidemic Sound",[842,59852,59853],{},"On first use, you'll be prompted to authenticate via OAuth:",[991,59855,59856,59859,59862,59865],{},[961,59857,59858],{},"Claude will open a browser window",[961,59860,59861],{},"Log in to your Epidemic Sound account",[961,59863,59864],{},"Authorize the MCP connection",[961,59866,59867],{},"Return to Claude - you're connected!",[1074,59869,59871],{"id":59870},"step-5-verify-the-connection","Step 5: Verify the Connection",[1013,59873,59875],{"className":1080,"code":59874,"language":1082,"meta":728,"style":728},"# List all configured MCP servers\nclaude mcp list\n\n# Check the Epidemic Sound connection\nclaude mcp get epidemicsound\n",[895,59876,59877,59882,59891,59895,59900],{"__ignoreMap":728},[1086,59878,59879],{"class":1088,"line":1089},[1086,59880,59881],{"class":1427},"# List all configured MCP servers\n",[1086,59883,59884,59886,59888],{"class":1088,"line":729},[1086,59885,6485],{"class":1092},[1086,59887,8553],{"class":1096},[1086,59889,59890],{"class":1096}," list\n",[1086,59892,59893],{"class":1088,"line":1112},[1086,59894,3390],{"emptyLinePlaceholder":738},[1086,59896,59897],{"class":1088,"line":1181},[1086,59898,59899],{"class":1427},"# Check the Epidemic Sound connection\n",[1086,59901,59902,59904,59906,59909],{"class":1088,"line":1205},[1086,59903,6485],{"class":1092},[1086,59905,8553],{"class":1096},[1086,59907,59908],{"class":1096}," get",[1086,59910,59911],{"class":1096}," epidemicsound\n",[842,59913,59914],{},"You should see the Epidemic Sound server listed and connected.",[1074,59916,59918],{"id":59917},"step-6-restart-claude","Step 6: Restart Claude",[842,59920,59921],{},"After configuration, restart Claude Desktop or your Claude Code session:",[1013,59923,59925],{"className":1080,"code":59924,"language":1082,"meta":728,"style":728},"# If using Claude Code, simply start a new session\nclaude\n",[895,59926,59927,59932],{"__ignoreMap":728},[1086,59928,59929],{"class":1088,"line":1089},[1086,59930,59931],{"class":1427},"# If using Claude Code, simply start a new session\n",[1086,59933,59934],{"class":1088,"line":729},[1086,59935,59936],{"class":1092},"claude\n",[4937,59938],{},[863,59940,59942],{"id":59941},"using-epidemic-sound-mcp-in-practice","Using Epidemic Sound MCP in Practice",[1572,59944,59945],{},[842,59946,59947],{},"The example conversations below show illustrative responses. Actual results from the Epidemic Sound MCP will vary based on the current catalog and your search terms.",[1074,59949,59951],{"id":59950},"basic-music-search","Basic Music Search",[842,59953,59954],{},"Once connected, you can search for music using natural language:",[1013,59956,59959],{"className":59957,"code":59958,"language":1018},[1016],"You: Find energetic rock tracks for a racing game\n\nClaude: I found several tracks that would work well for a racing game:\n\n1. \"Full Throttle\" by Horizon Blue\n   - Genre: Rock/Alternative\n   - BPM: 140\n   - Duration: 3:24\n   - Mood: Energetic, Driving\n\n2. \"Speed Demon\" by Electric Avenue\n   - Genre: Hard Rock\n   - BPM: 155\n   - Duration: 2:58\n   - Mood: Intense, Powerful\n\n[Preview links and download options...]\n",[895,59960,59958],{"__ignoreMap":728},[1074,59962,59964],{"id":59963},"contextual-search-for-game-development","Contextual Search for Game Development",[1013,59966,59969],{"className":59967,"code":59968,"language":1018},[1016],"You: I'm building a memory matching game for kids. I need 8 distinct\nshort musical clips that are easy to remember and differentiate.\nEach should be about 5-10 seconds, family-friendly, and cover\ndifferent musical styles.\n\nClaude: Here are 8 distinct clips perfect for a memory game:\n\n1. Classical Piano Melody - Gentle, recognizable tune\n2. Upbeat Ukulele Strum - Happy, playful feel\n3. Jazz Saxophone Riff - Smooth, memorable hook\n4. Electronic Synth Pulse - Modern, catchy beat\n5. Acoustic Guitar Fingerpicking - Warm, folk-style\n6. Orchestral Strings Swell - Dramatic, cinematic\n7. Reggae Bass Groove - Relaxed, rhythmic pattern\n8. Flute & Harp Fantasy - Magical, whimsical tone\n\nEach clip has been selected for maximum memorability and distinction...\n",[895,59970,59968],{"__ignoreMap":728},[1074,59972,59974],{"id":59973},"finding-sound-effects","Finding Sound Effects",[1013,59976,59979],{"className":59977,"code":59978,"language":1018},[1016],"You: Search for UI sound effects - button clicks, success chimes,\nand error sounds for a mobile app\n\nClaude: Found these UI sound effects:\n\nButton Clicks:\n- \"Soft Click\" - Subtle, modern feel\n- \"Mechanical Press\" - Tactile feedback sound\n- \"Digital Tap\" - Clean, minimalist\n\nSuccess Sounds:\n- \"Achievement Unlocked\" - Celebratory chime\n- \"Level Complete\" - Uplifting melody\n- \"Coin Collect\" - Classic game feedback\n\nError/Alert Sounds:\n- \"Gentle Warning\" - Non-intrusive alert\n- \"Soft Buzz\" - Subtle error indicator\n- \"Try Again\" - Encouraging tone\n",[895,59980,59978],{"__ignoreMap":728},[4937,59982],{},[863,59984,59986],{"id":59985},"integration-examples","Integration Examples",[1074,59988,59990],{"id":59989},"example-1-python-backend-with-fastapi","Example 1: Python Backend with FastAPI",[842,59992,59993],{},"After finding tracks via Claude, integrate them into your Python project:",[1013,59995,59997],{"className":1368,"code":59996,"language":1250,"meta":728,"style":728},"# music_config.py\n\nfrom dataclasses import dataclass\nfrom datetime import timedelta\nfrom typing import List\n\n\n@dataclass\nclass MusicTrack:\n    id: str\n    title: str\n    artist: str\n    file_path: str\n    duration: timedelta\n\n\n# Tracks discovered via Epidemic Sound MCP\nGAME_MUSIC_TRACKS: List[MusicTrack] = [\n    MusicTrack(\n        id=\"track_001\",\n        title=\"Puzzle Master\",\n        artist=\"Ambient Keys\",\n        file_path=\"assets/sounds/music/puzzle_master.mp3\",\n        duration=timedelta(seconds=12),\n    ),\n    MusicTrack(\n        id=\"track_002\",\n        title=\"Mind Games\",\n        artist=\"Electronic Dreams\",\n        file_path=\"assets/sounds/music/mind_games.mp3\",\n        duration=timedelta(seconds=10),\n    ),\n    # ... more tracks\n]\n\n\n# FastAPI endpoint example\nfrom fastapi import FastAPI\nfrom fastapi.responses import FileResponse\n\napp = FastAPI()\n\n\n@app.get(\"/api/tracks\")\nasync def get_tracks():\n    return [\n        {\n            \"id\": track.id,\n            \"title\": track.title,\n            \"artist\": track.artist,\n            \"duration_seconds\": track.duration.total_seconds(),\n        }\n        for track in GAME_MUSIC_TRACKS\n    ]\n\n\n@app.get(\"/api/tracks/{track_id}/stream\")\nasync def stream_track(track_id: str):\n    track = next((t for t in GAME_MUSIC_TRACKS if t.id == track_id), None)\n    if track:\n        return FileResponse(track.file_path, media_type=\"audio/mpeg\")\n    return {\"error\": \"Track not found\"}\n",[895,59998,59999,60004,60008,60020,60032,60044,60048,60052,60059,60068,60078,60087,60096,60105,60114,60118,60122,60127,60147,60154,60169,60185,60201,60217,60238,60243,60249,60264,60279,60294,60309,60327,60331,60336,60340,60344,60348,60353,60365,60382,60386,60398,60402,60406,60427,60438,60444,60449,60468,60486,60504,60527,60531,60543,60547,60551,60555,60582,60601,60643,60651,60682],{"__ignoreMap":728},[1086,60000,60001],{"class":1088,"line":1089},[1086,60002,60003],{"class":1427},"# music_config.py\n",[1086,60005,60006],{"class":1088,"line":729},[1086,60007,3390],{"emptyLinePlaceholder":738},[1086,60009,60010,60012,60015,60017],{"class":1088,"line":1112},[1086,60011,15570],{"class":1423},[1086,60013,60014],{"class":1436}," dataclasses ",[1086,60016,6503],{"class":1423},[1086,60018,60019],{"class":1436}," dataclass\n",[1086,60021,60022,60024,60027,60029],{"class":1088,"line":1181},[1086,60023,15570],{"class":1423},[1086,60025,60026],{"class":1436}," datetime ",[1086,60028,6503],{"class":1423},[1086,60030,60031],{"class":1436}," timedelta\n",[1086,60033,60034,60036,60039,60041],{"class":1088,"line":1205},[1086,60035,15570],{"class":1423},[1086,60037,60038],{"class":1436}," typing ",[1086,60040,6503],{"class":1423},[1086,60042,60043],{"class":1436}," List\n",[1086,60045,60046],{"class":1088,"line":1276},[1086,60047,3390],{"emptyLinePlaceholder":738},[1086,60049,60050],{"class":1088,"line":1282},[1086,60051,3390],{"emptyLinePlaceholder":738},[1086,60053,60054,60056],{"class":1088,"line":1288},[1086,60055,1376],{"class":1146},[1086,60057,60058],{"class":1105},"dataclass\n",[1086,60060,60061,60063,60066],{"class":1088,"line":2685},[1086,60062,4036],{"class":1155},[1086,60064,60065],{"class":1092}," MusicTrack",[1086,60067,1418],{"class":1146},[1086,60069,60070,60073,60075],{"class":1088,"line":2700},[1086,60071,60072],{"class":1105},"    id",[1086,60074,1133],{"class":1146},[1086,60076,60077],{"class":1092}," str\n",[1086,60079,60080,60083,60085],{"class":1088,"line":3398},[1086,60081,60082],{"class":1436},"    title",[1086,60084,1133],{"class":1146},[1086,60086,60077],{"class":1092},[1086,60088,60089,60092,60094],{"class":1088,"line":1715},[1086,60090,60091],{"class":1436},"    artist",[1086,60093,1133],{"class":1146},[1086,60095,60077],{"class":1092},[1086,60097,60098,60101,60103],{"class":1088,"line":3409},[1086,60099,60100],{"class":1436},"    file_path",[1086,60102,1133],{"class":1146},[1086,60104,60077],{"class":1092},[1086,60106,60107,60110,60112],{"class":1088,"line":3415},[1086,60108,60109],{"class":1436},"    duration",[1086,60111,1133],{"class":1146},[1086,60113,60031],{"class":1436},[1086,60115,60116],{"class":1088,"line":3421},[1086,60117,3390],{"emptyLinePlaceholder":738},[1086,60119,60120],{"class":1088,"line":3427},[1086,60121,3390],{"emptyLinePlaceholder":738},[1086,60123,60124],{"class":1088,"line":3433},[1086,60125,60126],{"class":1427},"# Tracks discovered via Epidemic Sound MCP\n",[1086,60128,60129,60132,60134,60136,60138,60141,60143,60145],{"class":1088,"line":3439},[1086,60130,60131],{"class":1436},"GAME_MUSIC_TRACKS",[1086,60133,1133],{"class":1146},[1086,60135,43515],{"class":1436},[1086,60137,4340],{"class":1146},[1086,60139,60140],{"class":1436},"MusicTrack",[1086,60142,4420],{"class":1146},[1086,60144,19552],{"class":1146},[1086,60146,6580],{"class":1146},[1086,60148,60149,60152],{"class":1088,"line":3444},[1086,60150,60151],{"class":1105},"    MusicTrack",[1086,60153,4094],{"class":1146},[1086,60155,60156,60158,60160,60162,60165,60167],{"class":1088,"line":3450},[1086,60157,22945],{"class":1401},[1086,60159,1440],{"class":1146},[1086,60161,1159],{"class":1146},[1086,60163,60164],{"class":1096},"track_001",[1086,60166,1159],{"class":1146},[1086,60168,1202],{"class":1146},[1086,60170,60171,60174,60176,60178,60181,60183],{"class":1088,"line":3456},[1086,60172,60173],{"class":1401},"        title",[1086,60175,1440],{"class":1146},[1086,60177,1159],{"class":1146},[1086,60179,60180],{"class":1096},"Puzzle Master",[1086,60182,1159],{"class":1146},[1086,60184,1202],{"class":1146},[1086,60186,60187,60190,60192,60194,60197,60199],{"class":1088,"line":3462},[1086,60188,60189],{"class":1401},"        artist",[1086,60191,1440],{"class":1146},[1086,60193,1159],{"class":1146},[1086,60195,60196],{"class":1096},"Ambient Keys",[1086,60198,1159],{"class":1146},[1086,60200,1202],{"class":1146},[1086,60202,60203,60206,60208,60210,60213,60215],{"class":1088,"line":3467},[1086,60204,60205],{"class":1401},"        file_path",[1086,60207,1440],{"class":1146},[1086,60209,1159],{"class":1146},[1086,60211,60212],{"class":1096},"assets/sounds/music/puzzle_master.mp3",[1086,60214,1159],{"class":1146},[1086,60216,1202],{"class":1146},[1086,60218,60219,60222,60224,60227,60229,60232,60234,60236],{"class":1088,"line":3473},[1086,60220,60221],{"class":1401},"        duration",[1086,60223,1440],{"class":1146},[1086,60225,60226],{"class":1105},"timedelta",[1086,60228,1398],{"class":1146},[1086,60230,60231],{"class":1401},"seconds",[1086,60233,1440],{"class":1146},[1086,60235,2522],{"class":1187},[1086,60237,20449],{"class":1146},[1086,60239,60240],{"class":1088,"line":3479},[1086,60241,60242],{"class":1146},"    ),\n",[1086,60244,60245,60247],{"class":1088,"line":3485},[1086,60246,60151],{"class":1105},[1086,60248,4094],{"class":1146},[1086,60250,60251,60253,60255,60257,60260,60262],{"class":1088,"line":3491},[1086,60252,22945],{"class":1401},[1086,60254,1440],{"class":1146},[1086,60256,1159],{"class":1146},[1086,60258,60259],{"class":1096},"track_002",[1086,60261,1159],{"class":1146},[1086,60263,1202],{"class":1146},[1086,60265,60266,60268,60270,60272,60275,60277],{"class":1088,"line":3497},[1086,60267,60173],{"class":1401},[1086,60269,1440],{"class":1146},[1086,60271,1159],{"class":1146},[1086,60273,60274],{"class":1096},"Mind Games",[1086,60276,1159],{"class":1146},[1086,60278,1202],{"class":1146},[1086,60280,60281,60283,60285,60287,60290,60292],{"class":1088,"line":3503},[1086,60282,60189],{"class":1401},[1086,60284,1440],{"class":1146},[1086,60286,1159],{"class":1146},[1086,60288,60289],{"class":1096},"Electronic Dreams",[1086,60291,1159],{"class":1146},[1086,60293,1202],{"class":1146},[1086,60295,60296,60298,60300,60302,60305,60307],{"class":1088,"line":3509},[1086,60297,60205],{"class":1401},[1086,60299,1440],{"class":1146},[1086,60301,1159],{"class":1146},[1086,60303,60304],{"class":1096},"assets/sounds/music/mind_games.mp3",[1086,60306,1159],{"class":1146},[1086,60308,1202],{"class":1146},[1086,60310,60311,60313,60315,60317,60319,60321,60323,60325],{"class":1088,"line":3515},[1086,60312,60221],{"class":1401},[1086,60314,1440],{"class":1146},[1086,60316,60226],{"class":1105},[1086,60318,1398],{"class":1146},[1086,60320,60231],{"class":1401},[1086,60322,1440],{"class":1146},[1086,60324,7284],{"class":1187},[1086,60326,20449],{"class":1146},[1086,60328,60329],{"class":1088,"line":3520},[1086,60330,60242],{"class":1146},[1086,60332,60333],{"class":1088,"line":3526},[1086,60334,60335],{"class":1427},"    # ... more tracks\n",[1086,60337,60338],{"class":1088,"line":3531},[1086,60339,1273],{"class":1146},[1086,60341,60342],{"class":1088,"line":3537},[1086,60343,3390],{"emptyLinePlaceholder":738},[1086,60345,60346],{"class":1088,"line":3543},[1086,60347,3390],{"emptyLinePlaceholder":738},[1086,60349,60350],{"class":1088,"line":3549},[1086,60351,60352],{"class":1427},"# FastAPI endpoint example\n",[1086,60354,60355,60357,60360,60362],{"class":1088,"line":3555},[1086,60356,15570],{"class":1423},[1086,60358,60359],{"class":1436}," fastapi ",[1086,60361,6503],{"class":1423},[1086,60363,60364],{"class":1436}," FastAPI\n",[1086,60366,60367,60369,60372,60374,60377,60379],{"class":1088,"line":3561},[1086,60368,15570],{"class":1423},[1086,60370,60371],{"class":1436}," fastapi",[1086,60373,861],{"class":1146},[1086,60375,60376],{"class":1436},"responses ",[1086,60378,6503],{"class":1423},[1086,60380,60381],{"class":1436}," FileResponse\n",[1086,60383,60384],{"class":1088,"line":3567},[1086,60385,3390],{"emptyLinePlaceholder":738},[1086,60387,60388,60391,60393,60396],{"class":1088,"line":17749},[1086,60389,60390],{"class":1436},"app ",[1086,60392,1440],{"class":1146},[1086,60394,60395],{"class":1105}," FastAPI",[1086,60397,1387],{"class":1146},[1086,60399,60400],{"class":1088,"line":19843},[1086,60401,3390],{"emptyLinePlaceholder":738},[1086,60403,60404],{"class":1088,"line":19848},[1086,60405,3390],{"emptyLinePlaceholder":738},[1086,60407,60408,60410,60412,60414,60416,60418,60420,60423,60425],{"class":1088,"line":19880},[1086,60409,1376],{"class":1146},[1086,60411,26462],{"class":1105},[1086,60413,861],{"class":1146},[1086,60415,10812],{"class":1105},[1086,60417,1398],{"class":1146},[1086,60419,1159],{"class":1146},[1086,60421,60422],{"class":1096},"/api/tracks",[1086,60424,1159],{"class":1146},[1086,60426,1455],{"class":1146},[1086,60428,60429,60431,60433,60436],{"class":1088,"line":19896},[1086,60430,26419],{"class":1155},[1086,60432,36861],{"class":1155},[1086,60434,60435],{"class":1105}," get_tracks",[1086,60437,5947],{"class":1146},[1086,60439,60440,60442],{"class":1088,"line":19919},[1086,60441,1460],{"class":1423},[1086,60443,6580],{"class":1146},[1086,60445,60446],{"class":1088,"line":19927},[1086,60447,60448],{"class":1146},"        {\n",[1086,60450,60451,60453,60455,60457,60459,60462,60464,60466],{"class":1088,"line":19948},[1086,60452,21603],{"class":1146},[1086,60454,4156],{"class":1096},[1086,60456,1159],{"class":1146},[1086,60458,1133],{"class":1146},[1086,60460,60461],{"class":1436}," track",[1086,60463,861],{"class":1146},[1086,60465,4156],{"class":4109},[1086,60467,1202],{"class":1146},[1086,60469,60470,60472,60474,60476,60478,60480,60482,60484],{"class":1088,"line":19968},[1086,60471,21603],{"class":1146},[1086,60473,9069],{"class":1096},[1086,60475,1159],{"class":1146},[1086,60477,1133],{"class":1146},[1086,60479,60461],{"class":1436},[1086,60481,861],{"class":1146},[1086,60483,9069],{"class":4109},[1086,60485,1202],{"class":1146},[1086,60487,60488,60490,60492,60494,60496,60498,60500,60502],{"class":1088,"line":19987},[1086,60489,21603],{"class":1146},[1086,60491,7377],{"class":1096},[1086,60493,1159],{"class":1146},[1086,60495,1133],{"class":1146},[1086,60497,60461],{"class":1436},[1086,60499,861],{"class":1146},[1086,60501,7377],{"class":4109},[1086,60503,1202],{"class":1146},[1086,60505,60506,60508,60510,60512,60514,60516,60518,60520,60522,60525],{"class":1088,"line":20007},[1086,60507,21603],{"class":1146},[1086,60509,8993],{"class":1096},[1086,60511,1159],{"class":1146},[1086,60513,1133],{"class":1146},[1086,60515,60461],{"class":1436},[1086,60517,861],{"class":1146},[1086,60519,30984],{"class":4109},[1086,60521,861],{"class":1146},[1086,60523,60524],{"class":1105},"total_seconds",[1086,60526,25628],{"class":1146},[1086,60528,60529],{"class":1088,"line":20013},[1086,60530,25533],{"class":1146},[1086,60532,60533,60535,60538,60540],{"class":1088,"line":20021},[1086,60534,6903],{"class":1423},[1086,60536,60537],{"class":1436}," track ",[1086,60539,5931],{"class":1423},[1086,60541,60542],{"class":1436}," GAME_MUSIC_TRACKS\n",[1086,60544,60545],{"class":1088,"line":20051},[1086,60546,27566],{"class":1146},[1086,60548,60549],{"class":1088,"line":20072},[1086,60550,3390],{"emptyLinePlaceholder":738},[1086,60552,60553],{"class":1088,"line":20077},[1086,60554,3390],{"emptyLinePlaceholder":738},[1086,60556,60557,60559,60561,60563,60565,60567,60569,60572,60575,60578,60580],{"class":1088,"line":20083},[1086,60558,1376],{"class":1146},[1086,60560,26462],{"class":1105},[1086,60562,861],{"class":1146},[1086,60564,10812],{"class":1105},[1086,60566,1398],{"class":1146},[1086,60568,1159],{"class":1146},[1086,60570,60571],{"class":1096},"/api/tracks/",[1086,60573,60574],{"class":1187},"{track_id}",[1086,60576,60577],{"class":1096},"/stream",[1086,60579,1159],{"class":1146},[1086,60581,1455],{"class":1146},[1086,60583,60584,60586,60588,60591,60593,60595,60597,60599],{"class":1088,"line":20095},[1086,60585,26419],{"class":1155},[1086,60587,36861],{"class":1155},[1086,60589,60590],{"class":1105}," stream_track",[1086,60592,1398],{"class":1146},[1086,60594,32479],{"class":1401},[1086,60596,1133],{"class":1146},[1086,60598,1407],{"class":1092},[1086,60600,4047],{"class":1146},[1086,60602,60603,60606,60608,60610,60613,60616,60618,60620,60622,60625,60627,60629,60631,60633,60635,60638,60640],{"class":1088,"line":20112},[1086,60604,60605],{"class":1436},"    track ",[1086,60607,1440],{"class":1146},[1086,60609,23349],{"class":1105},[1086,60611,60612],{"class":1146},"((",[1086,60614,60615],{"class":1105},"t ",[1086,60617,10799],{"class":1423},[1086,60619,47636],{"class":1105},[1086,60621,5931],{"class":1423},[1086,60623,60624],{"class":1105}," GAME_MUSIC_TRACKS ",[1086,60626,11056],{"class":1423},[1086,60628,47571],{"class":1105},[1086,60630,861],{"class":1146},[1086,60632,4156],{"class":4109},[1086,60634,10847],{"class":1146},[1086,60636,60637],{"class":1105}," track_id",[1086,60639,4179],{"class":1146},[1086,60641,60642],{"class":1146}," None)\n",[1086,60644,60645,60647,60649],{"class":1088,"line":20117},[1086,60646,6474],{"class":1423},[1086,60648,60461],{"class":1436},[1086,60650,1418],{"class":1146},[1086,60652,60653,60655,60658,60660,60662,60664,60666,60668,60671,60673,60675,60678,60680],{"class":1088,"line":20139},[1086,60654,4239],{"class":1423},[1086,60656,60657],{"class":1105}," FileResponse",[1086,60659,1398],{"class":1146},[1086,60661,33099],{"class":1105},[1086,60663,861],{"class":1146},[1086,60665,35660],{"class":4109},[1086,60667,1227],{"class":1146},[1086,60669,60670],{"class":1401}," media_type",[1086,60672,1440],{"class":1146},[1086,60674,1159],{"class":1146},[1086,60676,60677],{"class":1096},"audio/mpeg",[1086,60679,1159],{"class":1146},[1086,60681,1455],{"class":1146},[1086,60683,60684,60686,60688,60690,60693,60695,60697,60699,60702,60704],{"class":1088,"line":20169},[1086,60685,1460],{"class":1423},[1086,60687,4520],{"class":1146},[1086,60689,1159],{"class":1146},[1086,60691,60692],{"class":1096},"error",[1086,60694,1159],{"class":1146},[1086,60696,1133],{"class":1146},[1086,60698,1195],{"class":1146},[1086,60700,60701],{"class":1096},"Track not found",[1086,60703,1159],{"class":1146},[1086,60705,1291],{"class":1146},[1074,60707,60709],{"id":60708},"example-2-vue-3-nuxt-ui-audio-player","Example 2: Vue 3 / Nuxt UI Audio Player",[1013,60711,60713],{"className":27398,"code":60712,"language":27401,"meta":728,"style":728},"\u003C!-- components/MusicPlayer.vue -->\n\u003Ctemplate>\n  \u003CUCard>\n    \u003Ctemplate #header>\n      \u003Cdiv class=\"flex items-center justify-between\">\n        \u003Ch3 class=\"text-lg font-semibold\">{{ currentTrack?.title }}\u003C/h3>\n        \u003CUBadge color=\"primary\">{{ currentTrack?.artist }}\u003C/UBadge>\n      \u003C/div>\n    \u003C/template>\n\n    \u003Cdiv class=\"space-y-4\">\n      \u003C!-- Progress bar -->\n      \u003CUProgress :value=\"progress\" />\n\n      \u003C!-- Controls -->\n      \u003Cdiv class=\"flex items-center justify-center gap-4\">\n        \u003CUButton\n          icon=\"i-heroicons-backward\"\n          color=\"gray\"\n          variant=\"ghost\"\n          @click=\"previousTrack\"\n        />\n        \u003CUButton\n          :icon=\"isPlaying ? 'i-heroicons-pause' : 'i-heroicons-play'\"\n          size=\"lg\"\n          @click=\"togglePlay\"\n        />\n        \u003CUButton\n          icon=\"i-heroicons-forward\"\n          color=\"gray\"\n          variant=\"ghost\"\n          @click=\"nextTrack\"\n        />\n      \u003C/div>\n\n      \u003C!-- Track list -->\n      \u003CUDivider />\n      \u003Cdiv class=\"space-y-2\">\n        \u003CUButton\n          v-for=\"track in tracks\"\n          :key=\"track.id\"\n          block\n          :color=\"currentTrack?.id === track.id ? 'primary' : 'gray'\"\n          variant=\"soft\"\n          @click=\"selectTrack(track)\"\n        >\n          {{ track.title }} - {{ track.artist }}\n        \u003C/UButton>\n      \u003C/div>\n    \u003C/div>\n  \u003C/UCard>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\ninterface MusicTrack {\n  id: string\n  title: string\n  artist: string\n  src: string\n  category: string\n  mood: string\n  bpm: number\n}\n\n// Tracks curated via Epidemic Sound MCP\nconst tracks: MusicTrack[] = [\n  {\n    id: 'es_001',\n    title: 'Digital Sunrise',\n    artist: 'Synth Wave Collective',\n    src: '/audio/music/digital_sunrise.mp3',\n    category: 'electronic',\n    mood: 'uplifting',\n    bpm: 120,\n  },\n  {\n    id: 'es_002',\n    title: 'Ocean Breeze',\n    artist: 'Ambient Nature',\n    src: '/audio/music/ocean_breeze.mp3',\n    category: 'ambient',\n    mood: 'calm',\n    bpm: 70,\n  },\n]\n\nconst currentTrack = ref\u003CMusicTrack | null>(tracks[0])\nconst isPlaying = ref(false)\nconst progress = ref(0)\nconst audio = ref\u003CHTMLAudioElement | null>(null)\n\nonMounted(() => {\n  audio.value = new Audio()\n  audio.value.addEventListener('timeupdate', updateProgress)\n  audio.value.addEventListener('ended', nextTrack)\n})\n\nfunction updateProgress() {\n  if (audio.value) {\n    progress.value = (audio.value.currentTime / audio.value.duration) * 100\n  }\n}\n\nfunction selectTrack(track: MusicTrack) {\n  currentTrack.value = track\n  if (audio.value) {\n    audio.value.src = track.src\n    audio.value.play()\n    isPlaying.value = true\n  }\n}\n\nfunction togglePlay() {\n  if (!audio.value || !currentTrack.value) return\n\n  if (isPlaying.value) {\n    audio.value.pause()\n  } else {\n    if (!audio.value.src) {\n      audio.value.src = currentTrack.value.src\n    }\n    audio.value.play()\n  }\n  isPlaying.value = !isPlaying.value\n}\n\nfunction nextTrack() {\n  const currentIndex = tracks.findIndex(t => t.id === currentTrack.value?.id)\n  const nextIndex = (currentIndex + 1) % tracks.length\n  selectTrack(tracks[nextIndex])\n}\n\nfunction previousTrack() {\n  const currentIndex = tracks.findIndex(t => t.id === currentTrack.value?.id)\n  const prevIndex = currentIndex === 0 ? tracks.length - 1 : currentIndex - 1\n  selectTrack(tracks[prevIndex])\n}\n\u003C/script>\n",[895,60714,60715,60720,60729,60738,60752,60772,60800,60829,60837,60845,60849,60868,60873,60895,60899,60904,60923,60930,60944,60958,60972,60986,60991,60997,61011,61025,61038,61042,61048,61061,61073,61085,61098,61102,61110,61114,61119,61128,61146,61152,61166,61180,61185,61199,61212,61225,61230,61235,61245,61253,61261,61269,61277,61281,61305,61314,61324,61333,61342,61351,61360,61369,61378,61382,61386,61391,61409,61414,61429,61444,61459,61475,61491,61507,61517,61521,61525,61540,61555,61570,61585,61600,61615,61626,61630,61634,61638,61667,61685,61703,61732,61737,61751,61770,61800,61829,61836,61841,61852,61869,61912,61917,61922,61927,61947,61962,61979,62003,62019,62034,62039,62044,62049,62061,62093,62098,62116,62132,62141,62164,62191,62196,62211,62216,62237,62242,62247,62258,62303,62334,62352,62357,62362,62374,62415,62452,62468,62473],{"__ignoreMap":728},[1086,60716,60717],{"class":1088,"line":1089},[1086,60718,60719],{"class":1427},"\u003C!-- components/MusicPlayer.vue -->\n",[1086,60721,60722,60724,60727],{"class":1088,"line":729},[1086,60723,11164],{"class":1146},[1086,60725,60726],{"class":4109},"template",[1086,60728,11170],{"class":1146},[1086,60730,60731,60733,60736],{"class":1088,"line":1112},[1086,60732,11180],{"class":1146},[1086,60734,60735],{"class":4109},"UCard",[1086,60737,11170],{"class":1146},[1086,60739,60740,60742,60744,60747,60750],{"class":1088,"line":1181},[1086,60741,11190],{"class":1146},[1086,60743,60726],{"class":4109},[1086,60745,60746],{"class":1146}," #",[1086,60748,60749],{"class":1155},"header",[1086,60751,11170],{"class":1146},[1086,60753,60754,60756,60758,60761,60763,60765,60768,60770],{"class":1088,"line":1205},[1086,60755,17895],{"class":1146},[1086,60757,1045],{"class":4109},[1086,60759,60760],{"class":1155}," class",[1086,60762,1440],{"class":1146},[1086,60764,1159],{"class":1146},[1086,60766,60767],{"class":1096},"flex items-center justify-between",[1086,60769,1159],{"class":1146},[1086,60771,11170],{"class":1146},[1086,60773,60774,60776,60778,60780,60782,60784,60787,60789,60791,60794,60796,60798],{"class":1088,"line":1276},[1086,60775,18297],{"class":1146},[1086,60777,1074],{"class":4109},[1086,60779,60760],{"class":1155},[1086,60781,1440],{"class":1146},[1086,60783,1159],{"class":1146},[1086,60785,60786],{"class":1096},"text-lg font-semibold",[1086,60788,1159],{"class":1146},[1086,60790,2694],{"class":1146},[1086,60792,60793],{"class":1436},"{{ currentTrack?.title }}",[1086,60795,11201],{"class":1146},[1086,60797,1074],{"class":4109},[1086,60799,11170],{"class":1146},[1086,60801,60802,60804,60807,60810,60812,60814,60816,60818,60820,60823,60825,60827],{"class":1088,"line":1282},[1086,60803,18297],{"class":1146},[1086,60805,60806],{"class":4109},"UBadge",[1086,60808,60809],{"class":1155}," color",[1086,60811,1440],{"class":1146},[1086,60813,1159],{"class":1146},[1086,60815,50243],{"class":1096},[1086,60817,1159],{"class":1146},[1086,60819,2694],{"class":1146},[1086,60821,60822],{"class":1436},"{{ currentTrack?.artist }}",[1086,60824,11201],{"class":1146},[1086,60826,60806],{"class":4109},[1086,60828,11170],{"class":1146},[1086,60830,60831,60833,60835],{"class":1088,"line":1288},[1086,60832,18314],{"class":1146},[1086,60834,1045],{"class":4109},[1086,60836,11170],{"class":1146},[1086,60838,60839,60841,60843],{"class":1088,"line":2685},[1086,60840,17914],{"class":1146},[1086,60842,60726],{"class":4109},[1086,60844,11170],{"class":1146},[1086,60846,60847],{"class":1088,"line":2700},[1086,60848,3390],{"emptyLinePlaceholder":738},[1086,60850,60851,60853,60855,60857,60859,60861,60864,60866],{"class":1088,"line":3398},[1086,60852,11190],{"class":1146},[1086,60854,1045],{"class":4109},[1086,60856,60760],{"class":1155},[1086,60858,1440],{"class":1146},[1086,60860,1159],{"class":1146},[1086,60862,60863],{"class":1096},"space-y-4",[1086,60865,1159],{"class":1146},[1086,60867,11170],{"class":1146},[1086,60869,60870],{"class":1088,"line":1715},[1086,60871,60872],{"class":1427},"      \u003C!-- Progress bar -->\n",[1086,60874,60875,60877,60880,60883,60885,60887,60890,60892],{"class":1088,"line":3409},[1086,60876,17895],{"class":1146},[1086,60878,60879],{"class":4109},"UProgress",[1086,60881,60882],{"class":1155}," :value",[1086,60884,1440],{"class":1146},[1086,60886,1159],{"class":1146},[1086,60888,60889],{"class":1096},"progress",[1086,60891,1159],{"class":1146},[1086,60893,60894],{"class":1146}," />\n",[1086,60896,60897],{"class":1088,"line":3415},[1086,60898,3390],{"emptyLinePlaceholder":738},[1086,60900,60901],{"class":1088,"line":3421},[1086,60902,60903],{"class":1427},"      \u003C!-- Controls -->\n",[1086,60905,60906,60908,60910,60912,60914,60916,60919,60921],{"class":1088,"line":3427},[1086,60907,17895],{"class":1146},[1086,60909,1045],{"class":4109},[1086,60911,60760],{"class":1155},[1086,60913,1440],{"class":1146},[1086,60915,1159],{"class":1146},[1086,60917,60918],{"class":1096},"flex items-center justify-center gap-4",[1086,60920,1159],{"class":1146},[1086,60922,11170],{"class":1146},[1086,60924,60925,60927],{"class":1088,"line":3433},[1086,60926,18297],{"class":1146},[1086,60928,60929],{"class":4109},"UButton\n",[1086,60931,60932,60935,60937,60939,60942],{"class":1088,"line":3439},[1086,60933,60934],{"class":1155},"          icon",[1086,60936,1440],{"class":1146},[1086,60938,1159],{"class":1146},[1086,60940,60941],{"class":1096},"i-heroicons-backward",[1086,60943,4441],{"class":1146},[1086,60945,60946,60949,60951,60953,60956],{"class":1088,"line":3444},[1086,60947,60948],{"class":1155},"          color",[1086,60950,1440],{"class":1146},[1086,60952,1159],{"class":1146},[1086,60954,60955],{"class":1096},"gray",[1086,60957,4441],{"class":1146},[1086,60959,60960,60963,60965,60967,60970],{"class":1088,"line":3450},[1086,60961,60962],{"class":1155},"          variant",[1086,60964,1440],{"class":1146},[1086,60966,1159],{"class":1146},[1086,60968,60969],{"class":1096},"ghost",[1086,60971,4441],{"class":1146},[1086,60973,60974,60977,60979,60981,60984],{"class":1088,"line":3456},[1086,60975,60976],{"class":1155},"          @click",[1086,60978,1440],{"class":1146},[1086,60980,1159],{"class":1146},[1086,60982,60983],{"class":1096},"previousTrack",[1086,60985,4441],{"class":1146},[1086,60987,60988],{"class":1088,"line":3462},[1086,60989,60990],{"class":1146},"        />\n",[1086,60992,60993,60995],{"class":1088,"line":3467},[1086,60994,18297],{"class":1146},[1086,60996,60929],{"class":4109},[1086,60998,60999,61002,61004,61006,61009],{"class":1088,"line":3473},[1086,61000,61001],{"class":1155},"          :icon",[1086,61003,1440],{"class":1146},[1086,61005,1159],{"class":1146},[1086,61007,61008],{"class":1096},"isPlaying ? 'i-heroicons-pause' : 'i-heroicons-play'",[1086,61010,4441],{"class":1146},[1086,61012,61013,61016,61018,61020,61023],{"class":1088,"line":3479},[1086,61014,61015],{"class":1155},"          size",[1086,61017,1440],{"class":1146},[1086,61019,1159],{"class":1146},[1086,61021,61022],{"class":1096},"lg",[1086,61024,4441],{"class":1146},[1086,61026,61027,61029,61031,61033,61036],{"class":1088,"line":3485},[1086,61028,60976],{"class":1155},[1086,61030,1440],{"class":1146},[1086,61032,1159],{"class":1146},[1086,61034,61035],{"class":1096},"togglePlay",[1086,61037,4441],{"class":1146},[1086,61039,61040],{"class":1088,"line":3491},[1086,61041,60990],{"class":1146},[1086,61043,61044,61046],{"class":1088,"line":3497},[1086,61045,18297],{"class":1146},[1086,61047,60929],{"class":4109},[1086,61049,61050,61052,61054,61056,61059],{"class":1088,"line":3503},[1086,61051,60934],{"class":1155},[1086,61053,1440],{"class":1146},[1086,61055,1159],{"class":1146},[1086,61057,61058],{"class":1096},"i-heroicons-forward",[1086,61060,4441],{"class":1146},[1086,61062,61063,61065,61067,61069,61071],{"class":1088,"line":3509},[1086,61064,60948],{"class":1155},[1086,61066,1440],{"class":1146},[1086,61068,1159],{"class":1146},[1086,61070,60955],{"class":1096},[1086,61072,4441],{"class":1146},[1086,61074,61075,61077,61079,61081,61083],{"class":1088,"line":3515},[1086,61076,60962],{"class":1155},[1086,61078,1440],{"class":1146},[1086,61080,1159],{"class":1146},[1086,61082,60969],{"class":1096},[1086,61084,4441],{"class":1146},[1086,61086,61087,61089,61091,61093,61096],{"class":1088,"line":3520},[1086,61088,60976],{"class":1155},[1086,61090,1440],{"class":1146},[1086,61092,1159],{"class":1146},[1086,61094,61095],{"class":1096},"nextTrack",[1086,61097,4441],{"class":1146},[1086,61099,61100],{"class":1088,"line":3526},[1086,61101,60990],{"class":1146},[1086,61103,61104,61106,61108],{"class":1088,"line":3531},[1086,61105,18314],{"class":1146},[1086,61107,1045],{"class":4109},[1086,61109,11170],{"class":1146},[1086,61111,61112],{"class":1088,"line":3537},[1086,61113,3390],{"emptyLinePlaceholder":738},[1086,61115,61116],{"class":1088,"line":3543},[1086,61117,61118],{"class":1427},"      \u003C!-- Track list -->\n",[1086,61120,61121,61123,61126],{"class":1088,"line":3549},[1086,61122,17895],{"class":1146},[1086,61124,61125],{"class":4109},"UDivider",[1086,61127,60894],{"class":1146},[1086,61129,61130,61132,61134,61136,61138,61140,61142,61144],{"class":1088,"line":3555},[1086,61131,17895],{"class":1146},[1086,61133,1045],{"class":4109},[1086,61135,60760],{"class":1155},[1086,61137,1440],{"class":1146},[1086,61139,1159],{"class":1146},[1086,61141,8184],{"class":1096},[1086,61143,1159],{"class":1146},[1086,61145,11170],{"class":1146},[1086,61147,61148,61150],{"class":1088,"line":3561},[1086,61149,18297],{"class":1146},[1086,61151,60929],{"class":4109},[1086,61153,61154,61157,61159,61161,61164],{"class":1088,"line":3567},[1086,61155,61156],{"class":1155},"          v-for",[1086,61158,1440],{"class":1146},[1086,61160,1159],{"class":1146},[1086,61162,61163],{"class":1096},"track in tracks",[1086,61165,4441],{"class":1146},[1086,61167,61168,61171,61173,61175,61178],{"class":1088,"line":17749},[1086,61169,61170],{"class":1155},"          :key",[1086,61172,1440],{"class":1146},[1086,61174,1159],{"class":1146},[1086,61176,61177],{"class":1096},"track.id",[1086,61179,4441],{"class":1146},[1086,61181,61182],{"class":1088,"line":19843},[1086,61183,61184],{"class":1155},"          block\n",[1086,61186,61187,61190,61192,61194,61197],{"class":1088,"line":19848},[1086,61188,61189],{"class":1155},"          :color",[1086,61191,1440],{"class":1146},[1086,61193,1159],{"class":1146},[1086,61195,61196],{"class":1096},"currentTrack?.id === track.id ? 'primary' : 'gray'",[1086,61198,4441],{"class":1146},[1086,61200,61201,61203,61205,61207,61210],{"class":1088,"line":19880},[1086,61202,60962],{"class":1155},[1086,61204,1440],{"class":1146},[1086,61206,1159],{"class":1146},[1086,61208,61209],{"class":1096},"soft",[1086,61211,4441],{"class":1146},[1086,61213,61214,61216,61218,61220,61223],{"class":1088,"line":19896},[1086,61215,60976],{"class":1155},[1086,61217,1440],{"class":1146},[1086,61219,1159],{"class":1146},[1086,61221,61222],{"class":1096},"selectTrack(track)",[1086,61224,4441],{"class":1146},[1086,61226,61227],{"class":1088,"line":19919},[1086,61228,61229],{"class":1146},"        >\n",[1086,61231,61232],{"class":1088,"line":19927},[1086,61233,61234],{"class":1436},"          {{ track.title }} - {{ track.artist }}\n",[1086,61236,61237,61240,61243],{"class":1088,"line":19948},[1086,61238,61239],{"class":1146},"        \u003C/",[1086,61241,61242],{"class":4109},"UButton",[1086,61244,11170],{"class":1146},[1086,61246,61247,61249,61251],{"class":1088,"line":19968},[1086,61248,18314],{"class":1146},[1086,61250,1045],{"class":4109},[1086,61252,11170],{"class":1146},[1086,61254,61255,61257,61259],{"class":1088,"line":19987},[1086,61256,17914],{"class":1146},[1086,61258,1045],{"class":4109},[1086,61260,11170],{"class":1146},[1086,61262,61263,61265,61267],{"class":1088,"line":20007},[1086,61264,11260],{"class":1146},[1086,61266,60735],{"class":4109},[1086,61268,11170],{"class":1146},[1086,61270,61271,61273,61275],{"class":1088,"line":20013},[1086,61272,11201],{"class":1146},[1086,61274,60726],{"class":4109},[1086,61276,11170],{"class":1146},[1086,61278,61279],{"class":1088,"line":20021},[1086,61280,3390],{"emptyLinePlaceholder":738},[1086,61282,61283,61285,61288,61291,61294,61296,61298,61301,61303],{"class":1088,"line":20051},[1086,61284,11164],{"class":1146},[1086,61286,61287],{"class":4109},"script",[1086,61289,61290],{"class":1155}," setup",[1086,61292,61293],{"class":1155}," lang",[1086,61295,1440],{"class":1146},[1086,61297,1159],{"class":1146},[1086,61299,61300],{"class":1096},"ts",[1086,61302,1159],{"class":1146},[1086,61304,11170],{"class":1146},[1086,61306,61307,61310,61312],{"class":1088,"line":20072},[1086,61308,61309],{"class":1155},"interface",[1086,61311,60065],{"class":1092},[1086,61313,1164],{"class":1146},[1086,61315,61316,61319,61321],{"class":1088,"line":20077},[1086,61317,61318],{"class":4109},"  id",[1086,61320,1133],{"class":1146},[1086,61322,61323],{"class":1092}," string\n",[1086,61325,61326,61329,61331],{"class":1088,"line":20083},[1086,61327,61328],{"class":4109},"  title",[1086,61330,1133],{"class":1146},[1086,61332,61323],{"class":1092},[1086,61334,61335,61338,61340],{"class":1088,"line":20095},[1086,61336,61337],{"class":4109},"  artist",[1086,61339,1133],{"class":1146},[1086,61341,61323],{"class":1092},[1086,61343,61344,61347,61349],{"class":1088,"line":20112},[1086,61345,61346],{"class":4109},"  src",[1086,61348,1133],{"class":1146},[1086,61350,61323],{"class":1092},[1086,61352,61353,61356,61358],{"class":1088,"line":20117},[1086,61354,61355],{"class":4109},"  category",[1086,61357,1133],{"class":1146},[1086,61359,61323],{"class":1092},[1086,61361,61362,61365,61367],{"class":1088,"line":20139},[1086,61363,61364],{"class":4109},"  mood",[1086,61366,1133],{"class":1146},[1086,61368,61323],{"class":1092},[1086,61370,61371,61373,61375],{"class":1088,"line":20169},[1086,61372,36006],{"class":4109},[1086,61374,1133],{"class":1146},[1086,61376,61377],{"class":1092}," number\n",[1086,61379,61380],{"class":1088,"line":20197},[1086,61381,1291],{"class":1146},[1086,61383,61384],{"class":1088,"line":20227},[1086,61385,3390],{"emptyLinePlaceholder":738},[1086,61387,61388],{"class":1088,"line":20232},[1086,61389,61390],{"class":1427},"// Tracks curated via Epidemic Sound MCP\n",[1086,61392,61393,61395,61398,61400,61402,61405,61407],{"class":1088,"line":23544},[1086,61394,33442],{"class":1155},[1086,61396,61397],{"class":1436}," tracks",[1086,61399,1133],{"class":1146},[1086,61401,60065],{"class":1092},[1086,61403,61404],{"class":1436},"[] ",[1086,61406,1440],{"class":1146},[1086,61408,6580],{"class":1436},[1086,61410,61411],{"class":1088,"line":23560},[1086,61412,61413],{"class":1146},"  {\n",[1086,61415,61416,61418,61420,61422,61425,61427],{"class":1088,"line":23565},[1086,61417,60072],{"class":4109},[1086,61419,1133],{"class":1146},[1086,61421,26405],{"class":1146},[1086,61423,61424],{"class":1096},"es_001",[1086,61426,10742],{"class":1146},[1086,61428,1202],{"class":1146},[1086,61430,61431,61433,61435,61437,61440,61442],{"class":1088,"line":23570},[1086,61432,60082],{"class":4109},[1086,61434,1133],{"class":1146},[1086,61436,26405],{"class":1146},[1086,61438,61439],{"class":1096},"Digital Sunrise",[1086,61441,10742],{"class":1146},[1086,61443,1202],{"class":1146},[1086,61445,61446,61448,61450,61452,61455,61457],{"class":1088,"line":23593},[1086,61447,60091],{"class":4109},[1086,61449,1133],{"class":1146},[1086,61451,26405],{"class":1146},[1086,61453,61454],{"class":1096},"Synth Wave Collective",[1086,61456,10742],{"class":1146},[1086,61458,1202],{"class":1146},[1086,61460,61461,61464,61466,61468,61471,61473],{"class":1088,"line":23613},[1086,61462,61463],{"class":4109},"    src",[1086,61465,1133],{"class":1146},[1086,61467,26405],{"class":1146},[1086,61469,61470],{"class":1096},"/audio/music/digital_sunrise.mp3",[1086,61472,10742],{"class":1146},[1086,61474,1202],{"class":1146},[1086,61476,61477,61480,61482,61484,61487,61489],{"class":1088,"line":23644},[1086,61478,61479],{"class":4109},"    category",[1086,61481,1133],{"class":1146},[1086,61483,26405],{"class":1146},[1086,61485,61486],{"class":1096},"electronic",[1086,61488,10742],{"class":1146},[1086,61490,1202],{"class":1146},[1086,61492,61493,61496,61498,61500,61503,61505],{"class":1088,"line":23649},[1086,61494,61495],{"class":4109},"    mood",[1086,61497,1133],{"class":1146},[1086,61499,26405],{"class":1146},[1086,61501,61502],{"class":1096},"uplifting",[1086,61504,10742],{"class":1146},[1086,61506,1202],{"class":1146},[1086,61508,61509,61511,61513,61515],{"class":1088,"line":23655},[1086,61510,36270],{"class":4109},[1086,61512,1133],{"class":1146},[1086,61514,47943],{"class":1187},[1086,61516,1202],{"class":1146},[1086,61518,61519],{"class":1088,"line":23670},[1086,61520,27571],{"class":1146},[1086,61522,61523],{"class":1088,"line":23679},[1086,61524,61413],{"class":1146},[1086,61526,61527,61529,61531,61533,61536,61538],{"class":1088,"line":23691},[1086,61528,60072],{"class":4109},[1086,61530,1133],{"class":1146},[1086,61532,26405],{"class":1146},[1086,61534,61535],{"class":1096},"es_002",[1086,61537,10742],{"class":1146},[1086,61539,1202],{"class":1146},[1086,61541,61542,61544,61546,61548,61551,61553],{"class":1088,"line":23708},[1086,61543,60082],{"class":4109},[1086,61545,1133],{"class":1146},[1086,61547,26405],{"class":1146},[1086,61549,61550],{"class":1096},"Ocean Breeze",[1086,61552,10742],{"class":1146},[1086,61554,1202],{"class":1146},[1086,61556,61557,61559,61561,61563,61566,61568],{"class":1088,"line":23724},[1086,61558,60091],{"class":4109},[1086,61560,1133],{"class":1146},[1086,61562,26405],{"class":1146},[1086,61564,61565],{"class":1096},"Ambient Nature",[1086,61567,10742],{"class":1146},[1086,61569,1202],{"class":1146},[1086,61571,61572,61574,61576,61578,61581,61583],{"class":1088,"line":23737},[1086,61573,61463],{"class":4109},[1086,61575,1133],{"class":1146},[1086,61577,26405],{"class":1146},[1086,61579,61580],{"class":1096},"/audio/music/ocean_breeze.mp3",[1086,61582,10742],{"class":1146},[1086,61584,1202],{"class":1146},[1086,61586,61587,61589,61591,61593,61596,61598],{"class":1088,"line":23742},[1086,61588,61479],{"class":4109},[1086,61590,1133],{"class":1146},[1086,61592,26405],{"class":1146},[1086,61594,61595],{"class":1096},"ambient",[1086,61597,10742],{"class":1146},[1086,61599,1202],{"class":1146},[1086,61601,61602,61604,61606,61608,61611,61613],{"class":1088,"line":23747},[1086,61603,61495],{"class":4109},[1086,61605,1133],{"class":1146},[1086,61607,26405],{"class":1146},[1086,61609,61610],{"class":1096},"calm",[1086,61612,10742],{"class":1146},[1086,61614,1202],{"class":1146},[1086,61616,61617,61619,61621,61624],{"class":1088,"line":23763},[1086,61618,36270],{"class":4109},[1086,61620,1133],{"class":1146},[1086,61622,61623],{"class":1187}," 70",[1086,61625,1202],{"class":1146},[1086,61627,61628],{"class":1088,"line":23792},[1086,61629,27571],{"class":1146},[1086,61631,61632],{"class":1088,"line":23814},[1086,61633,1273],{"class":1436},[1086,61635,61636],{"class":1088,"line":23835},[1086,61637,3390],{"emptyLinePlaceholder":738},[1086,61639,61640,61642,61645,61647,61649,61651,61653,61655,61658,61660,61663,61665],{"class":1088,"line":23866},[1086,61641,33442],{"class":1155},[1086,61643,61644],{"class":1436}," currentTrack ",[1086,61646,1440],{"class":1146},[1086,61648,26382],{"class":1105},[1086,61650,11164],{"class":1146},[1086,61652,60140],{"class":1092},[1086,61654,11883],{"class":1146},[1086,61656,61657],{"class":1092}," null",[1086,61659,2694],{"class":1146},[1086,61661,61662],{"class":1436},"(tracks[",[1086,61664,4417],{"class":1187},[1086,61666,23233],{"class":1436},[1086,61668,61669,61671,61674,61676,61678,61680,61683],{"class":1088,"line":23872},[1086,61670,33442],{"class":1155},[1086,61672,61673],{"class":1436}," isPlaying ",[1086,61675,1440],{"class":1146},[1086,61677,26382],{"class":1105},[1086,61679,1398],{"class":1436},[1086,61681,61682],{"class":27021},"false",[1086,61684,1455],{"class":1436},[1086,61686,61688,61690,61693,61695,61697,61699,61701],{"class":1088,"line":61687},89,[1086,61689,33442],{"class":1155},[1086,61691,61692],{"class":1436}," progress ",[1086,61694,1440],{"class":1146},[1086,61696,26382],{"class":1105},[1086,61698,1398],{"class":1436},[1086,61700,4417],{"class":1187},[1086,61702,1455],{"class":1436},[1086,61704,61706,61708,61711,61713,61715,61717,61720,61722,61724,61726,61728,61730],{"class":1088,"line":61705},90,[1086,61707,33442],{"class":1155},[1086,61709,61710],{"class":1436}," audio ",[1086,61712,1440],{"class":1146},[1086,61714,26382],{"class":1105},[1086,61716,11164],{"class":1146},[1086,61718,61719],{"class":1092},"HTMLAudioElement",[1086,61721,11883],{"class":1146},[1086,61723,61657],{"class":1092},[1086,61725,2694],{"class":1146},[1086,61727,1398],{"class":1436},[1086,61729,22513],{"class":1146},[1086,61731,1455],{"class":1436},[1086,61733,61735],{"class":1088,"line":61734},91,[1086,61736,3390],{"emptyLinePlaceholder":738},[1086,61738,61740,61743,61745,61747,61749],{"class":1088,"line":61739},92,[1086,61741,61742],{"class":1105},"onMounted",[1086,61744,1398],{"class":1436},[1086,61746,2516],{"class":1146},[1086,61748,26610],{"class":1155},[1086,61750,1164],{"class":1146},[1086,61752,61754,61757,61759,61761,61763,61765,61768],{"class":1088,"line":61753},93,[1086,61755,61756],{"class":1436},"  audio",[1086,61758,861],{"class":1146},[1086,61760,26662],{"class":1436},[1086,61762,19552],{"class":1146},[1086,61764,26591],{"class":1146},[1086,61766,61767],{"class":1105}," Audio",[1086,61769,1387],{"class":4109},[1086,61771,61773,61775,61777,61779,61781,61784,61786,61788,61791,61793,61795,61798],{"class":1088,"line":61772},94,[1086,61774,61756],{"class":1436},[1086,61776,861],{"class":1146},[1086,61778,26662],{"class":1436},[1086,61780,861],{"class":1146},[1086,61782,61783],{"class":1105},"addEventListener",[1086,61785,1398],{"class":4109},[1086,61787,10742],{"class":1146},[1086,61789,61790],{"class":1096},"timeupdate",[1086,61792,10742],{"class":1146},[1086,61794,1227],{"class":1146},[1086,61796,61797],{"class":1436}," updateProgress",[1086,61799,1455],{"class":4109},[1086,61801,61803,61805,61807,61809,61811,61813,61815,61817,61820,61822,61824,61827],{"class":1088,"line":61802},95,[1086,61804,61756],{"class":1436},[1086,61806,861],{"class":1146},[1086,61808,26662],{"class":1436},[1086,61810,861],{"class":1146},[1086,61812,61783],{"class":1105},[1086,61814,1398],{"class":4109},[1086,61816,10742],{"class":1146},[1086,61818,61819],{"class":1096},"ended",[1086,61821,10742],{"class":1146},[1086,61823,1227],{"class":1146},[1086,61825,61826],{"class":1436}," nextTrack",[1086,61828,1455],{"class":4109},[1086,61830,61832,61834],{"class":1088,"line":61831},96,[1086,61833,4423],{"class":1146},[1086,61835,1455],{"class":1436},[1086,61837,61839],{"class":1088,"line":61838},97,[1086,61840,3390],{"emptyLinePlaceholder":738},[1086,61842,61844,61846,61848,61850],{"class":1088,"line":61843},98,[1086,61845,35934],{"class":1155},[1086,61847,61797],{"class":1105},[1086,61849,2516],{"class":1146},[1086,61851,1164],{"class":1146},[1086,61853,61855,61857,61859,61861,61863,61865,61867],{"class":1088,"line":61854},99,[1086,61856,27233],{"class":1423},[1086,61858,5979],{"class":4109},[1086,61860,787],{"class":1436},[1086,61862,861],{"class":1146},[1086,61864,26662],{"class":1436},[1086,61866,2778],{"class":4109},[1086,61868,1147],{"class":1146},[1086,61870,61872,61875,61877,61879,61881,61883,61885,61887,61889,61891,61894,61896,61898,61900,61902,61904,61906,61908,61910],{"class":1088,"line":61871},100,[1086,61873,61874],{"class":1436},"    progress",[1086,61876,861],{"class":1146},[1086,61878,26662],{"class":1436},[1086,61880,19552],{"class":1146},[1086,61882,5979],{"class":4109},[1086,61884,787],{"class":1436},[1086,61886,861],{"class":1146},[1086,61888,26662],{"class":1436},[1086,61890,861],{"class":1146},[1086,61892,61893],{"class":1436},"currentTime",[1086,61895,26689],{"class":1146},[1086,61897,48321],{"class":1436},[1086,61899,861],{"class":1146},[1086,61901,26662],{"class":1436},[1086,61903,861],{"class":1146},[1086,61905,30984],{"class":1436},[1086,61907,2778],{"class":4109},[1086,61909,2775],{"class":1146},[1086,61911,6364],{"class":1187},[1086,61913,61915],{"class":1088,"line":61914},101,[1086,61916,1285],{"class":1146},[1086,61918,61920],{"class":1088,"line":61919},102,[1086,61921,1291],{"class":1146},[1086,61923,61925],{"class":1088,"line":61924},103,[1086,61926,3390],{"emptyLinePlaceholder":738},[1086,61928,61930,61932,61935,61937,61939,61941,61943,61945],{"class":1088,"line":61929},104,[1086,61931,35934],{"class":1155},[1086,61933,61934],{"class":1105}," selectTrack",[1086,61936,1398],{"class":1146},[1086,61938,33099],{"class":1401},[1086,61940,1133],{"class":1146},[1086,61942,60065],{"class":1092},[1086,61944,1410],{"class":1146},[1086,61946,1164],{"class":1146},[1086,61948,61950,61953,61955,61957,61959],{"class":1088,"line":61949},105,[1086,61951,61952],{"class":1436},"  currentTrack",[1086,61954,861],{"class":1146},[1086,61956,26662],{"class":1436},[1086,61958,19552],{"class":1146},[1086,61960,61961],{"class":1436}," track\n",[1086,61963,61965,61967,61969,61971,61973,61975,61977],{"class":1088,"line":61964},106,[1086,61966,27233],{"class":1423},[1086,61968,5979],{"class":4109},[1086,61970,787],{"class":1436},[1086,61972,861],{"class":1146},[1086,61974,26662],{"class":1436},[1086,61976,2778],{"class":4109},[1086,61978,1147],{"class":1146},[1086,61980,61982,61985,61987,61989,61991,61994,61996,61998,62000],{"class":1088,"line":61981},107,[1086,61983,61984],{"class":1436},"    audio",[1086,61986,861],{"class":1146},[1086,61988,26662],{"class":1436},[1086,61990,861],{"class":1146},[1086,61992,61993],{"class":1436},"src",[1086,61995,19552],{"class":1146},[1086,61997,60461],{"class":1436},[1086,61999,861],{"class":1146},[1086,62001,62002],{"class":1436},"src\n",[1086,62004,62006,62008,62010,62012,62014,62017],{"class":1088,"line":62005},108,[1086,62007,61984],{"class":1436},[1086,62009,861],{"class":1146},[1086,62011,26662],{"class":1436},[1086,62013,861],{"class":1146},[1086,62015,62016],{"class":1105},"play",[1086,62018,1387],{"class":4109},[1086,62020,62022,62025,62027,62029,62031],{"class":1088,"line":62021},109,[1086,62023,62024],{"class":1436},"    isPlaying",[1086,62026,861],{"class":1146},[1086,62028,26662],{"class":1436},[1086,62030,19552],{"class":1146},[1086,62032,62033],{"class":27021}," true\n",[1086,62035,62037],{"class":1088,"line":62036},110,[1086,62038,1285],{"class":1146},[1086,62040,62042],{"class":1088,"line":62041},111,[1086,62043,1291],{"class":1146},[1086,62045,62047],{"class":1088,"line":62046},112,[1086,62048,3390],{"emptyLinePlaceholder":738},[1086,62050,62052,62054,62057,62059],{"class":1088,"line":62051},113,[1086,62053,35934],{"class":1155},[1086,62055,62056],{"class":1105}," togglePlay",[1086,62058,2516],{"class":1146},[1086,62060,1164],{"class":1146},[1086,62062,62064,62066,62068,62070,62072,62074,62076,62078,62081,62084,62086,62088,62090],{"class":1088,"line":62063},114,[1086,62065,27233],{"class":1423},[1086,62067,5979],{"class":4109},[1086,62069,38428],{"class":1146},[1086,62071,787],{"class":1436},[1086,62073,861],{"class":1146},[1086,62075,26662],{"class":1436},[1086,62077,38433],{"class":1146},[1086,62079,62080],{"class":1146}," !",[1086,62082,62083],{"class":1436},"currentTrack",[1086,62085,861],{"class":1146},[1086,62087,26662],{"class":1436},[1086,62089,2778],{"class":4109},[1086,62091,62092],{"class":1423},"return\n",[1086,62094,62096],{"class":1088,"line":62095},115,[1086,62097,3390],{"emptyLinePlaceholder":738},[1086,62099,62101,62103,62105,62108,62110,62112,62114],{"class":1088,"line":62100},116,[1086,62102,27233],{"class":1423},[1086,62104,5979],{"class":4109},[1086,62106,62107],{"class":1436},"isPlaying",[1086,62109,861],{"class":1146},[1086,62111,26662],{"class":1436},[1086,62113,2778],{"class":4109},[1086,62115,1147],{"class":1146},[1086,62117,62119,62121,62123,62125,62127,62130],{"class":1088,"line":62118},117,[1086,62120,61984],{"class":1436},[1086,62122,861],{"class":1146},[1086,62124,26662],{"class":1436},[1086,62126,861],{"class":1146},[1086,62128,62129],{"class":1105},"pause",[1086,62131,1387],{"class":4109},[1086,62133,62135,62137,62139],{"class":1088,"line":62134},118,[1086,62136,4797],{"class":1146},[1086,62138,36749],{"class":1423},[1086,62140,1164],{"class":1146},[1086,62142,62144,62146,62148,62150,62152,62154,62156,62158,62160,62162],{"class":1088,"line":62143},119,[1086,62145,6474],{"class":1423},[1086,62147,5979],{"class":4109},[1086,62149,38428],{"class":1146},[1086,62151,787],{"class":1436},[1086,62153,861],{"class":1146},[1086,62155,26662],{"class":1436},[1086,62157,861],{"class":1146},[1086,62159,61993],{"class":1436},[1086,62161,2778],{"class":4109},[1086,62163,1147],{"class":1146},[1086,62165,62167,62170,62172,62174,62176,62178,62180,62183,62185,62187,62189],{"class":1088,"line":62166},120,[1086,62168,62169],{"class":1436},"      audio",[1086,62171,861],{"class":1146},[1086,62173,26662],{"class":1436},[1086,62175,861],{"class":1146},[1086,62177,61993],{"class":1436},[1086,62179,19552],{"class":1146},[1086,62181,62182],{"class":1436}," currentTrack",[1086,62184,861],{"class":1146},[1086,62186,26662],{"class":1436},[1086,62188,861],{"class":1146},[1086,62190,62002],{"class":1436},[1086,62192,62194],{"class":1088,"line":62193},121,[1086,62195,1279],{"class":1146},[1086,62197,62199,62201,62203,62205,62207,62209],{"class":1088,"line":62198},122,[1086,62200,61984],{"class":1436},[1086,62202,861],{"class":1146},[1086,62204,26662],{"class":1436},[1086,62206,861],{"class":1146},[1086,62208,62016],{"class":1105},[1086,62210,1387],{"class":4109},[1086,62212,62214],{"class":1088,"line":62213},123,[1086,62215,1285],{"class":1146},[1086,62217,62219,62222,62224,62226,62228,62230,62232,62234],{"class":1088,"line":62218},124,[1086,62220,62221],{"class":1436},"  isPlaying",[1086,62223,861],{"class":1146},[1086,62225,26662],{"class":1436},[1086,62227,19552],{"class":1146},[1086,62229,62080],{"class":1146},[1086,62231,62107],{"class":1436},[1086,62233,861],{"class":1146},[1086,62235,62236],{"class":1436},"value\n",[1086,62238,62240],{"class":1088,"line":62239},125,[1086,62241,1291],{"class":1146},[1086,62243,62245],{"class":1088,"line":62244},126,[1086,62246,3390],{"emptyLinePlaceholder":738},[1086,62248,62250,62252,62254,62256],{"class":1088,"line":62249},127,[1086,62251,35934],{"class":1155},[1086,62253,61826],{"class":1105},[1086,62255,2516],{"class":1146},[1086,62257,1164],{"class":1146},[1086,62259,62261,62263,62266,62268,62270,62272,62275,62277,62280,62282,62284,62286,62288,62290,62292,62294,62296,62299,62301],{"class":1088,"line":62260},128,[1086,62262,26450],{"class":1155},[1086,62264,62265],{"class":1436}," currentIndex",[1086,62267,19552],{"class":1146},[1086,62269,61397],{"class":1436},[1086,62271,861],{"class":1146},[1086,62273,62274],{"class":1105},"findIndex",[1086,62276,1398],{"class":4109},[1086,62278,62279],{"class":1401},"t",[1086,62281,26610],{"class":1155},[1086,62283,47571],{"class":1436},[1086,62285,861],{"class":1146},[1086,62287,4156],{"class":1436},[1086,62289,27244],{"class":1146},[1086,62291,62182],{"class":1436},[1086,62293,861],{"class":1146},[1086,62295,26662],{"class":1436},[1086,62297,62298],{"class":1146},"?.",[1086,62300,4156],{"class":1436},[1086,62302,1455],{"class":4109},[1086,62304,62306,62308,62311,62313,62315,62318,62320,62322,62324,62327,62329,62331],{"class":1088,"line":62305},129,[1086,62307,26450],{"class":1155},[1086,62309,62310],{"class":1436}," nextIndex",[1086,62312,19552],{"class":1146},[1086,62314,5979],{"class":4109},[1086,62316,62317],{"class":1436},"currentIndex",[1086,62319,33787],{"class":1146},[1086,62321,23488],{"class":1187},[1086,62323,2778],{"class":4109},[1086,62325,62326],{"class":1146},"%",[1086,62328,61397],{"class":1436},[1086,62330,861],{"class":1146},[1086,62332,62333],{"class":1436},"length\n",[1086,62335,62337,62340,62342,62345,62347,62350],{"class":1088,"line":62336},130,[1086,62338,62339],{"class":1105},"  selectTrack",[1086,62341,1398],{"class":4109},[1086,62343,62344],{"class":1436},"tracks",[1086,62346,4340],{"class":4109},[1086,62348,62349],{"class":1436},"nextIndex",[1086,62351,23233],{"class":4109},[1086,62353,62355],{"class":1088,"line":62354},131,[1086,62356,1291],{"class":1146},[1086,62358,62360],{"class":1088,"line":62359},132,[1086,62361,3390],{"emptyLinePlaceholder":738},[1086,62363,62365,62367,62370,62372],{"class":1088,"line":62364},133,[1086,62366,35934],{"class":1155},[1086,62368,62369],{"class":1105}," previousTrack",[1086,62371,2516],{"class":1146},[1086,62373,1164],{"class":1146},[1086,62375,62377,62379,62381,62383,62385,62387,62389,62391,62393,62395,62397,62399,62401,62403,62405,62407,62409,62411,62413],{"class":1088,"line":62376},134,[1086,62378,26450],{"class":1155},[1086,62380,62265],{"class":1436},[1086,62382,19552],{"class":1146},[1086,62384,61397],{"class":1436},[1086,62386,861],{"class":1146},[1086,62388,62274],{"class":1105},[1086,62390,1398],{"class":4109},[1086,62392,62279],{"class":1401},[1086,62394,26610],{"class":1155},[1086,62396,47571],{"class":1436},[1086,62398,861],{"class":1146},[1086,62400,4156],{"class":1436},[1086,62402,27244],{"class":1146},[1086,62404,62182],{"class":1436},[1086,62406,861],{"class":1146},[1086,62408,26662],{"class":1436},[1086,62410,62298],{"class":1146},[1086,62412,4156],{"class":1436},[1086,62414,1455],{"class":4109},[1086,62416,62418,62420,62423,62425,62427,62429,62431,62433,62435,62437,62439,62441,62443,62445,62447,62449],{"class":1088,"line":62417},135,[1086,62419,26450],{"class":1155},[1086,62421,62422],{"class":1436}," prevIndex",[1086,62424,19552],{"class":1146},[1086,62426,62265],{"class":1436},[1086,62428,27244],{"class":1146},[1086,62430,22718],{"class":1187},[1086,62432,33835],{"class":1146},[1086,62434,61397],{"class":1436},[1086,62436,861],{"class":1146},[1086,62438,38671],{"class":1436},[1086,62440,2816],{"class":1146},[1086,62442,23488],{"class":1187},[1086,62444,33844],{"class":1146},[1086,62446,62265],{"class":1436},[1086,62448,2816],{"class":1146},[1086,62450,62451],{"class":1187}," 1\n",[1086,62453,62455,62457,62459,62461,62463,62466],{"class":1088,"line":62454},136,[1086,62456,62339],{"class":1105},[1086,62458,1398],{"class":4109},[1086,62460,62344],{"class":1436},[1086,62462,4340],{"class":4109},[1086,62464,62465],{"class":1436},"prevIndex",[1086,62467,23233],{"class":4109},[1086,62469,62471],{"class":1088,"line":62470},137,[1086,62472,1291],{"class":1146},[1086,62474,62476,62478,62480],{"class":1088,"line":62475},138,[1086,62477,11201],{"class":1146},[1086,62479,61287],{"class":4109},[1086,62481,11170],{"class":1146},[1074,62483,62485],{"id":62484},"example-3-typescript-music-service","Example 3: TypeScript Music Service",[1013,62487,62489],{"className":4602,"code":62488,"language":4605,"meta":728,"style":728},"// services/musicService.ts\n\ninterface MusicTrack {\n  id: string\n  title: string\n  artist: string\n  filePath: string\n  mood: string\n  bpm: number\n  duration: number\n}\n\ninterface SearchFilters {\n  mood?: string\n  minBpm?: number\n  maxBpm?: number\n  category?: string\n}\n\nclass MusicService {\n  private tracks: MusicTrack[] = []\n  private audioContext: AudioContext | null = null\n  private currentSource: AudioBufferSourceNode | null = null\n\n  constructor() {\n    // Tracks curated via Epidemic Sound MCP\n    this.tracks = [\n      {\n        id: 'puzzle_001',\n        title: 'Puzzle Master',\n        artist: 'Ambient Keys',\n        filePath: '/audio/puzzle_master.mp3',\n        mood: 'calm',\n        bpm: 85,\n        duration: 120,\n      },\n      {\n        id: 'action_001',\n        title: 'Speed Runner',\n        artist: 'Electric Dreams',\n        filePath: '/audio/speed_runner.mp3',\n        mood: 'energetic',\n        bpm: 140,\n        duration: 180,\n      },\n    ]\n  }\n\n  searchTracks(filters: SearchFilters): MusicTrack[] {\n    return this.tracks.filter(track => {\n      if (filters.mood && track.mood !== filters.mood) return false\n      if (filters.minBpm && track.bpm \u003C filters.minBpm) return false\n      if (filters.maxBpm && track.bpm > filters.maxBpm) return false\n      return true\n    })\n  }\n\n  async playTrack(trackId: string): Promise\u003Cvoid> {\n    const track = this.tracks.find(t => t.id === trackId)\n    if (!track) throw new Error('Track not found')\n\n    if (!this.audioContext) {\n      this.audioContext = new AudioContext()\n    }\n\n    // Stop current playback\n    this.stop()\n\n    const response = await fetch(track.filePath)\n    const arrayBuffer = await response.arrayBuffer()\n    const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer)\n\n    this.currentSource = this.audioContext.createBufferSource()\n    this.currentSource.buffer = audioBuffer\n    this.currentSource.connect(this.audioContext.destination)\n    this.currentSource.start()\n  }\n\n  stop(): void {\n    if (this.currentSource) {\n      this.currentSource.stop()\n      this.currentSource = null\n    }\n  }\n\n  getRandomTrack(mood?: string): MusicTrack {\n    const filtered = mood\n      ? this.tracks.filter(t => t.mood === mood)\n      : this.tracks\n    return filtered[Math.floor(Math.random() * filtered.length)]\n  }\n}\n\nexport const musicService = new MusicService()\n",[895,62490,62491,62496,62500,62508,62516,62524,62532,62541,62549,62557,62566,62570,62574,62583,62592,62601,62610,62618,62622,62626,62635,62652,62672,62692,62696,62705,62710,62721,62726,62741,62755,62769,62785,62800,62811,62821,62825,62829,62844,62859,62874,62889,62904,62915,62926,62930,62934,62938,62942,62964,62985,63025,63060,63096,63102,63108,63112,63116,63146,63183,63213,63217,63233,63248,63252,63256,63261,63270,63274,63297,63317,63343,63347,63367,63383,63406,63418,63422,63426,63438,63452,63464,63474,63478,63482,63486,63505,63517,63549,63558,63596,63600,63604,63608],{"__ignoreMap":728},[1086,62492,62493],{"class":1088,"line":1089},[1086,62494,62495],{"class":1427},"// services/musicService.ts\n",[1086,62497,62498],{"class":1088,"line":729},[1086,62499,3390],{"emptyLinePlaceholder":738},[1086,62501,62502,62504,62506],{"class":1088,"line":1112},[1086,62503,61309],{"class":1155},[1086,62505,60065],{"class":1092},[1086,62507,1164],{"class":1146},[1086,62509,62510,62512,62514],{"class":1088,"line":1181},[1086,62511,61318],{"class":4109},[1086,62513,1133],{"class":1146},[1086,62515,61323],{"class":1092},[1086,62517,62518,62520,62522],{"class":1088,"line":1205},[1086,62519,61328],{"class":4109},[1086,62521,1133],{"class":1146},[1086,62523,61323],{"class":1092},[1086,62525,62526,62528,62530],{"class":1088,"line":1276},[1086,62527,61337],{"class":4109},[1086,62529,1133],{"class":1146},[1086,62531,61323],{"class":1092},[1086,62533,62534,62537,62539],{"class":1088,"line":1282},[1086,62535,62536],{"class":4109},"  filePath",[1086,62538,1133],{"class":1146},[1086,62540,61323],{"class":1092},[1086,62542,62543,62545,62547],{"class":1088,"line":1288},[1086,62544,61364],{"class":4109},[1086,62546,1133],{"class":1146},[1086,62548,61323],{"class":1092},[1086,62550,62551,62553,62555],{"class":1088,"line":2685},[1086,62552,36006],{"class":4109},[1086,62554,1133],{"class":1146},[1086,62556,61377],{"class":1092},[1086,62558,62559,62562,62564],{"class":1088,"line":2700},[1086,62560,62561],{"class":4109},"  duration",[1086,62563,1133],{"class":1146},[1086,62565,61377],{"class":1092},[1086,62567,62568],{"class":1088,"line":3398},[1086,62569,1291],{"class":1146},[1086,62571,62572],{"class":1088,"line":1715},[1086,62573,3390],{"emptyLinePlaceholder":738},[1086,62575,62576,62578,62581],{"class":1088,"line":3409},[1086,62577,61309],{"class":1155},[1086,62579,62580],{"class":1092}," SearchFilters",[1086,62582,1164],{"class":1146},[1086,62584,62585,62587,62590],{"class":1088,"line":3415},[1086,62586,61364],{"class":4109},[1086,62588,62589],{"class":1146},"?:",[1086,62591,61323],{"class":1092},[1086,62593,62594,62597,62599],{"class":1088,"line":3421},[1086,62595,62596],{"class":4109},"  minBpm",[1086,62598,62589],{"class":1146},[1086,62600,61377],{"class":1092},[1086,62602,62603,62606,62608],{"class":1088,"line":3427},[1086,62604,62605],{"class":4109},"  maxBpm",[1086,62607,62589],{"class":1146},[1086,62609,61377],{"class":1092},[1086,62611,62612,62614,62616],{"class":1088,"line":3433},[1086,62613,61355],{"class":4109},[1086,62615,62589],{"class":1146},[1086,62617,61323],{"class":1092},[1086,62619,62620],{"class":1088,"line":3439},[1086,62621,1291],{"class":1146},[1086,62623,62624],{"class":1088,"line":3444},[1086,62625,3390],{"emptyLinePlaceholder":738},[1086,62627,62628,62630,62633],{"class":1088,"line":3450},[1086,62629,4036],{"class":1155},[1086,62631,62632],{"class":1092}," MusicService",[1086,62634,1164],{"class":1146},[1086,62636,62637,62640,62642,62644,62646,62648,62650],{"class":1088,"line":3456},[1086,62638,62639],{"class":1155},"  private",[1086,62641,61397],{"class":4109},[1086,62643,1133],{"class":1146},[1086,62645,60065],{"class":1092},[1086,62647,61404],{"class":1436},[1086,62649,1440],{"class":1146},[1086,62651,5920],{"class":1436},[1086,62653,62654,62656,62659,62661,62663,62665,62667,62669],{"class":1088,"line":3462},[1086,62655,62639],{"class":1155},[1086,62657,62658],{"class":4109}," audioContext",[1086,62660,1133],{"class":1146},[1086,62662,46798],{"class":1092},[1086,62664,11883],{"class":1146},[1086,62666,61657],{"class":1092},[1086,62668,19552],{"class":1146},[1086,62670,62671],{"class":1146}," null\n",[1086,62673,62674,62676,62679,62681,62684,62686,62688,62690],{"class":1088,"line":3467},[1086,62675,62639],{"class":1155},[1086,62677,62678],{"class":4109}," currentSource",[1086,62680,1133],{"class":1146},[1086,62682,62683],{"class":1092}," AudioBufferSourceNode",[1086,62685,11883],{"class":1146},[1086,62687,61657],{"class":1092},[1086,62689,19552],{"class":1146},[1086,62691,62671],{"class":1146},[1086,62693,62694],{"class":1088,"line":3473},[1086,62695,3390],{"emptyLinePlaceholder":738},[1086,62697,62698,62701,62703],{"class":1088,"line":3479},[1086,62699,62700],{"class":1155},"  constructor",[1086,62702,2516],{"class":1146},[1086,62704,1164],{"class":1146},[1086,62706,62707],{"class":1088,"line":3485},[1086,62708,62709],{"class":1427},"    // Tracks curated via Epidemic Sound MCP\n",[1086,62711,62712,62715,62717,62719],{"class":1088,"line":3491},[1086,62713,62714],{"class":1146},"    this.",[1086,62716,62344],{"class":1436},[1086,62718,19552],{"class":1146},[1086,62720,6580],{"class":4109},[1086,62722,62723],{"class":1088,"line":3497},[1086,62724,62725],{"class":1146},"      {\n",[1086,62727,62728,62730,62732,62734,62737,62739],{"class":1088,"line":3503},[1086,62729,22945],{"class":4109},[1086,62731,1133],{"class":1146},[1086,62733,26405],{"class":1146},[1086,62735,62736],{"class":1096},"puzzle_001",[1086,62738,10742],{"class":1146},[1086,62740,1202],{"class":1146},[1086,62742,62743,62745,62747,62749,62751,62753],{"class":1088,"line":3509},[1086,62744,60173],{"class":4109},[1086,62746,1133],{"class":1146},[1086,62748,26405],{"class":1146},[1086,62750,60180],{"class":1096},[1086,62752,10742],{"class":1146},[1086,62754,1202],{"class":1146},[1086,62756,62757,62759,62761,62763,62765,62767],{"class":1088,"line":3515},[1086,62758,60189],{"class":4109},[1086,62760,1133],{"class":1146},[1086,62762,26405],{"class":1146},[1086,62764,60196],{"class":1096},[1086,62766,10742],{"class":1146},[1086,62768,1202],{"class":1146},[1086,62770,62771,62774,62776,62778,62781,62783],{"class":1088,"line":3520},[1086,62772,62773],{"class":4109},"        filePath",[1086,62775,1133],{"class":1146},[1086,62777,26405],{"class":1146},[1086,62779,62780],{"class":1096},"/audio/puzzle_master.mp3",[1086,62782,10742],{"class":1146},[1086,62784,1202],{"class":1146},[1086,62786,62787,62790,62792,62794,62796,62798],{"class":1088,"line":3526},[1086,62788,62789],{"class":4109},"        mood",[1086,62791,1133],{"class":1146},[1086,62793,26405],{"class":1146},[1086,62795,61610],{"class":1096},[1086,62797,10742],{"class":1146},[1086,62799,1202],{"class":1146},[1086,62801,62802,62804,62806,62809],{"class":1088,"line":3531},[1086,62803,37043],{"class":4109},[1086,62805,1133],{"class":1146},[1086,62807,62808],{"class":1187}," 85",[1086,62810,1202],{"class":1146},[1086,62812,62813,62815,62817,62819],{"class":1088,"line":3537},[1086,62814,60221],{"class":4109},[1086,62816,1133],{"class":1146},[1086,62818,47943],{"class":1187},[1086,62820,1202],{"class":1146},[1086,62822,62823],{"class":1088,"line":3543},[1086,62824,26712],{"class":1146},[1086,62826,62827],{"class":1088,"line":3549},[1086,62828,62725],{"class":1146},[1086,62830,62831,62833,62835,62837,62840,62842],{"class":1088,"line":3555},[1086,62832,22945],{"class":4109},[1086,62834,1133],{"class":1146},[1086,62836,26405],{"class":1146},[1086,62838,62839],{"class":1096},"action_001",[1086,62841,10742],{"class":1146},[1086,62843,1202],{"class":1146},[1086,62845,62846,62848,62850,62852,62855,62857],{"class":1088,"line":3561},[1086,62847,60173],{"class":4109},[1086,62849,1133],{"class":1146},[1086,62851,26405],{"class":1146},[1086,62853,62854],{"class":1096},"Speed Runner",[1086,62856,10742],{"class":1146},[1086,62858,1202],{"class":1146},[1086,62860,62861,62863,62865,62867,62870,62872],{"class":1088,"line":3567},[1086,62862,60189],{"class":4109},[1086,62864,1133],{"class":1146},[1086,62866,26405],{"class":1146},[1086,62868,62869],{"class":1096},"Electric Dreams",[1086,62871,10742],{"class":1146},[1086,62873,1202],{"class":1146},[1086,62875,62876,62878,62880,62882,62885,62887],{"class":1088,"line":17749},[1086,62877,62773],{"class":4109},[1086,62879,1133],{"class":1146},[1086,62881,26405],{"class":1146},[1086,62883,62884],{"class":1096},"/audio/speed_runner.mp3",[1086,62886,10742],{"class":1146},[1086,62888,1202],{"class":1146},[1086,62890,62891,62893,62895,62897,62900,62902],{"class":1088,"line":19843},[1086,62892,62789],{"class":4109},[1086,62894,1133],{"class":1146},[1086,62896,26405],{"class":1146},[1086,62898,62899],{"class":1096},"energetic",[1086,62901,10742],{"class":1146},[1086,62903,1202],{"class":1146},[1086,62905,62906,62908,62910,62913],{"class":1088,"line":19848},[1086,62907,37043],{"class":4109},[1086,62909,1133],{"class":1146},[1086,62911,62912],{"class":1187}," 140",[1086,62914,1202],{"class":1146},[1086,62916,62917,62919,62921,62924],{"class":1088,"line":19880},[1086,62918,60221],{"class":4109},[1086,62920,1133],{"class":1146},[1086,62922,62923],{"class":1187}," 180",[1086,62925,1202],{"class":1146},[1086,62927,62928],{"class":1088,"line":19896},[1086,62929,26712],{"class":1146},[1086,62931,62932],{"class":1088,"line":19919},[1086,62933,27566],{"class":4109},[1086,62935,62936],{"class":1088,"line":19927},[1086,62937,1285],{"class":1146},[1086,62939,62940],{"class":1088,"line":19948},[1086,62941,3390],{"emptyLinePlaceholder":738},[1086,62943,62944,62947,62949,62952,62954,62956,62958,62960,62962],{"class":1088,"line":19968},[1086,62945,62946],{"class":4109},"  searchTracks",[1086,62948,1398],{"class":1146},[1086,62950,62951],{"class":1401},"filters",[1086,62953,1133],{"class":1146},[1086,62955,62580],{"class":1092},[1086,62957,4723],{"class":1146},[1086,62959,60065],{"class":1092},[1086,62961,61404],{"class":1436},[1086,62963,1147],{"class":1146},[1086,62965,62966,62968,62971,62973,62975,62977,62979,62981,62983],{"class":1088,"line":19987},[1086,62967,1460],{"class":1423},[1086,62969,62970],{"class":1146}," this.",[1086,62972,62344],{"class":1436},[1086,62974,861],{"class":1146},[1086,62976,36916],{"class":1105},[1086,62978,1398],{"class":4109},[1086,62980,33099],{"class":1401},[1086,62982,26610],{"class":1155},[1086,62984,1164],{"class":1146},[1086,62986,62987,62990,62992,62994,62996,62999,63002,63004,63006,63008,63011,63014,63016,63018,63020,63023],{"class":1088,"line":20007},[1086,62988,62989],{"class":1423},"      if",[1086,62991,5979],{"class":4109},[1086,62993,62951],{"class":1436},[1086,62995,861],{"class":1146},[1086,62997,62998],{"class":1436},"mood",[1086,63000,63001],{"class":1146}," &&",[1086,63003,60461],{"class":1436},[1086,63005,861],{"class":1146},[1086,63007,62998],{"class":1436},[1086,63009,63010],{"class":1146}," !==",[1086,63012,63013],{"class":1436}," filters",[1086,63015,861],{"class":1146},[1086,63017,62998],{"class":1436},[1086,63019,2778],{"class":4109},[1086,63021,63022],{"class":1423},"return",[1086,63024,35857],{"class":27021},[1086,63026,63027,63029,63031,63033,63035,63038,63040,63042,63044,63046,63048,63050,63052,63054,63056,63058],{"class":1088,"line":20013},[1086,63028,62989],{"class":1423},[1086,63030,5979],{"class":4109},[1086,63032,62951],{"class":1436},[1086,63034,861],{"class":1146},[1086,63036,63037],{"class":1436},"minBpm",[1086,63039,63001],{"class":1146},[1086,63041,60461],{"class":1436},[1086,63043,861],{"class":1146},[1086,63045,31015],{"class":1436},[1086,63047,38664],{"class":1146},[1086,63049,63013],{"class":1436},[1086,63051,861],{"class":1146},[1086,63053,63037],{"class":1436},[1086,63055,2778],{"class":4109},[1086,63057,63022],{"class":1423},[1086,63059,35857],{"class":27021},[1086,63061,63062,63064,63066,63068,63070,63073,63075,63077,63079,63081,63084,63086,63088,63090,63092,63094],{"class":1088,"line":20021},[1086,63063,62989],{"class":1423},[1086,63065,5979],{"class":4109},[1086,63067,62951],{"class":1436},[1086,63069,861],{"class":1146},[1086,63071,63072],{"class":1436},"maxBpm",[1086,63074,63001],{"class":1146},[1086,63076,60461],{"class":1436},[1086,63078,861],{"class":1146},[1086,63080,31015],{"class":1436},[1086,63082,63083],{"class":1146}," >",[1086,63085,63013],{"class":1436},[1086,63087,861],{"class":1146},[1086,63089,63072],{"class":1436},[1086,63091,2778],{"class":4109},[1086,63093,63022],{"class":1423},[1086,63095,35857],{"class":27021},[1086,63097,63098,63100],{"class":1088,"line":20051},[1086,63099,50959],{"class":1423},[1086,63101,62033],{"class":27021},[1086,63103,63104,63106],{"class":1088,"line":20072},[1086,63105,27075],{"class":1146},[1086,63107,1455],{"class":4109},[1086,63109,63110],{"class":1088,"line":20077},[1086,63111,1285],{"class":1146},[1086,63113,63114],{"class":1088,"line":20083},[1086,63115,3390],{"emptyLinePlaceholder":738},[1086,63117,63118,63121,63124,63126,63129,63131,63133,63135,63137,63139,63142,63144],{"class":1088,"line":20095},[1086,63119,63120],{"class":1155},"  async",[1086,63122,63123],{"class":4109}," playTrack",[1086,63125,1398],{"class":1146},[1086,63127,63128],{"class":1401},"trackId",[1086,63130,1133],{"class":1146},[1086,63132,4636],{"class":1092},[1086,63134,4723],{"class":1146},[1086,63136,4626],{"class":1092},[1086,63138,11164],{"class":1146},[1086,63140,63141],{"class":1092},"void",[1086,63143,2694],{"class":1146},[1086,63145,1164],{"class":1146},[1086,63147,63148,63151,63153,63155,63157,63159,63161,63164,63166,63168,63170,63172,63174,63176,63178,63181],{"class":1088,"line":20112},[1086,63149,63150],{"class":1155},"    const",[1086,63152,60461],{"class":1436},[1086,63154,19552],{"class":1146},[1086,63156,62970],{"class":1146},[1086,63158,62344],{"class":1436},[1086,63160,861],{"class":1146},[1086,63162,63163],{"class":1105},"find",[1086,63165,1398],{"class":4109},[1086,63167,62279],{"class":1401},[1086,63169,26610],{"class":1155},[1086,63171,47571],{"class":1436},[1086,63173,861],{"class":1146},[1086,63175,4156],{"class":1436},[1086,63177,27244],{"class":1146},[1086,63179,63180],{"class":1436}," trackId",[1086,63182,1455],{"class":4109},[1086,63184,63185,63187,63189,63191,63193,63195,63198,63200,63203,63205,63207,63209,63211],{"class":1088,"line":20117},[1086,63186,6474],{"class":1423},[1086,63188,5979],{"class":4109},[1086,63190,38428],{"class":1146},[1086,63192,33099],{"class":1436},[1086,63194,2778],{"class":4109},[1086,63196,63197],{"class":1423},"throw",[1086,63199,26591],{"class":1146},[1086,63201,63202],{"class":1105}," Error",[1086,63204,1398],{"class":4109},[1086,63206,10742],{"class":1146},[1086,63208,60701],{"class":1096},[1086,63210,10742],{"class":1146},[1086,63212,1455],{"class":4109},[1086,63214,63215],{"class":1088,"line":20139},[1086,63216,3390],{"emptyLinePlaceholder":738},[1086,63218,63219,63221,63223,63226,63229,63231],{"class":1088,"line":20169},[1086,63220,6474],{"class":1423},[1086,63222,5979],{"class":4109},[1086,63224,63225],{"class":1146},"!this.",[1086,63227,63228],{"class":1436},"audioContext",[1086,63230,2778],{"class":4109},[1086,63232,1147],{"class":1146},[1086,63234,63235,63238,63240,63242,63244,63246],{"class":1088,"line":20197},[1086,63236,63237],{"class":1146},"      this.",[1086,63239,63228],{"class":1436},[1086,63241,19552],{"class":1146},[1086,63243,26591],{"class":1146},[1086,63245,46798],{"class":1105},[1086,63247,1387],{"class":4109},[1086,63249,63250],{"class":1088,"line":20227},[1086,63251,1279],{"class":1146},[1086,63253,63254],{"class":1088,"line":20232},[1086,63255,3390],{"emptyLinePlaceholder":738},[1086,63257,63258],{"class":1088,"line":23544},[1086,63259,63260],{"class":1427},"    // Stop current playback\n",[1086,63262,63263,63265,63268],{"class":1088,"line":23560},[1086,63264,62714],{"class":1146},[1086,63266,63267],{"class":1105},"stop",[1086,63269,1387],{"class":4109},[1086,63271,63272],{"class":1088,"line":23565},[1086,63273,3390],{"emptyLinePlaceholder":738},[1086,63275,63276,63278,63280,63282,63284,63286,63288,63290,63292,63295],{"class":1088,"line":23570},[1086,63277,63150],{"class":1155},[1086,63279,36583],{"class":1436},[1086,63281,19552],{"class":1146},[1086,63283,4659],{"class":1423},[1086,63285,26890],{"class":1105},[1086,63287,1398],{"class":4109},[1086,63289,33099],{"class":1436},[1086,63291,861],{"class":1146},[1086,63293,63294],{"class":1436},"filePath",[1086,63296,1455],{"class":4109},[1086,63298,63299,63301,63304,63306,63308,63310,63312,63315],{"class":1088,"line":23593},[1086,63300,63150],{"class":1155},[1086,63302,63303],{"class":1436}," arrayBuffer",[1086,63305,19552],{"class":1146},[1086,63307,4659],{"class":1423},[1086,63309,36583],{"class":1436},[1086,63311,861],{"class":1146},[1086,63313,63314],{"class":1105},"arrayBuffer",[1086,63316,1387],{"class":4109},[1086,63318,63319,63321,63324,63326,63328,63330,63332,63334,63337,63339,63341],{"class":1088,"line":23613},[1086,63320,63150],{"class":1155},[1086,63322,63323],{"class":1436}," audioBuffer",[1086,63325,19552],{"class":1146},[1086,63327,4659],{"class":1423},[1086,63329,62970],{"class":1146},[1086,63331,63228],{"class":1436},[1086,63333,861],{"class":1146},[1086,63335,63336],{"class":1105},"decodeAudioData",[1086,63338,1398],{"class":4109},[1086,63340,63314],{"class":1436},[1086,63342,1455],{"class":4109},[1086,63344,63345],{"class":1088,"line":23644},[1086,63346,3390],{"emptyLinePlaceholder":738},[1086,63348,63349,63351,63354,63356,63358,63360,63362,63365],{"class":1088,"line":23649},[1086,63350,62714],{"class":1146},[1086,63352,63353],{"class":1436},"currentSource",[1086,63355,19552],{"class":1146},[1086,63357,62970],{"class":1146},[1086,63359,63228],{"class":1436},[1086,63361,861],{"class":1146},[1086,63363,63364],{"class":1105},"createBufferSource",[1086,63366,1387],{"class":4109},[1086,63368,63369,63371,63373,63375,63378,63380],{"class":1088,"line":23655},[1086,63370,62714],{"class":1146},[1086,63372,63353],{"class":1436},[1086,63374,861],{"class":1146},[1086,63376,63377],{"class":1436},"buffer",[1086,63379,19552],{"class":1146},[1086,63381,63382],{"class":1436}," audioBuffer\n",[1086,63384,63385,63387,63389,63391,63393,63395,63398,63400,63402,63404],{"class":1088,"line":23670},[1086,63386,62714],{"class":1146},[1086,63388,63353],{"class":1436},[1086,63390,861],{"class":1146},[1086,63392,46857],{"class":1105},[1086,63394,1398],{"class":4109},[1086,63396,63397],{"class":1146},"this.",[1086,63399,63228],{"class":1436},[1086,63401,861],{"class":1146},[1086,63403,27548],{"class":1436},[1086,63405,1455],{"class":4109},[1086,63407,63408,63410,63412,63414,63416],{"class":1088,"line":23679},[1086,63409,62714],{"class":1146},[1086,63411,63353],{"class":1436},[1086,63413,861],{"class":1146},[1086,63415,29437],{"class":1105},[1086,63417,1387],{"class":4109},[1086,63419,63420],{"class":1088,"line":23691},[1086,63421,1285],{"class":1146},[1086,63423,63424],{"class":1088,"line":23708},[1086,63425,3390],{"emptyLinePlaceholder":738},[1086,63427,63428,63431,63433,63436],{"class":1088,"line":23724},[1086,63429,63430],{"class":4109},"  stop",[1086,63432,4623],{"class":1146},[1086,63434,63435],{"class":1092}," void",[1086,63437,1164],{"class":1146},[1086,63439,63440,63442,63444,63446,63448,63450],{"class":1088,"line":23737},[1086,63441,6474],{"class":1423},[1086,63443,5979],{"class":4109},[1086,63445,63397],{"class":1146},[1086,63447,63353],{"class":1436},[1086,63449,2778],{"class":4109},[1086,63451,1147],{"class":1146},[1086,63453,63454,63456,63458,63460,63462],{"class":1088,"line":23742},[1086,63455,63237],{"class":1146},[1086,63457,63353],{"class":1436},[1086,63459,861],{"class":1146},[1086,63461,63267],{"class":1105},[1086,63463,1387],{"class":4109},[1086,63465,63466,63468,63470,63472],{"class":1088,"line":23747},[1086,63467,63237],{"class":1146},[1086,63469,63353],{"class":1436},[1086,63471,19552],{"class":1146},[1086,63473,62671],{"class":1146},[1086,63475,63476],{"class":1088,"line":23763},[1086,63477,1279],{"class":1146},[1086,63479,63480],{"class":1088,"line":23792},[1086,63481,1285],{"class":1146},[1086,63483,63484],{"class":1088,"line":23814},[1086,63485,3390],{"emptyLinePlaceholder":738},[1086,63487,63488,63491,63493,63495,63497,63499,63501,63503],{"class":1088,"line":23835},[1086,63489,63490],{"class":4109},"  getRandomTrack",[1086,63492,1398],{"class":1146},[1086,63494,62998],{"class":1401},[1086,63496,62589],{"class":1146},[1086,63498,4636],{"class":1092},[1086,63500,4723],{"class":1146},[1086,63502,60065],{"class":1092},[1086,63504,1164],{"class":1146},[1086,63506,63507,63509,63512,63514],{"class":1088,"line":23866},[1086,63508,63150],{"class":1155},[1086,63510,63511],{"class":1436}," filtered",[1086,63513,19552],{"class":1146},[1086,63515,63516],{"class":1436}," mood\n",[1086,63518,63519,63522,63524,63526,63528,63530,63532,63534,63536,63538,63540,63542,63544,63547],{"class":1088,"line":23872},[1086,63520,63521],{"class":1146},"      ?",[1086,63523,62970],{"class":1146},[1086,63525,62344],{"class":1436},[1086,63527,861],{"class":1146},[1086,63529,36916],{"class":1105},[1086,63531,1398],{"class":4109},[1086,63533,62279],{"class":1401},[1086,63535,26610],{"class":1155},[1086,63537,47571],{"class":1436},[1086,63539,861],{"class":1146},[1086,63541,62998],{"class":1436},[1086,63543,27244],{"class":1146},[1086,63545,63546],{"class":1436}," mood",[1086,63548,1455],{"class":4109},[1086,63550,63551,63554,63556],{"class":1088,"line":61687},[1086,63552,63553],{"class":1146},"      :",[1086,63555,62970],{"class":1146},[1086,63557,27273],{"class":1436},[1086,63559,63560,63562,63564,63566,63569,63571,63574,63576,63578,63580,63583,63585,63587,63589,63591,63593],{"class":1088,"line":61705},[1086,63561,1460],{"class":1423},[1086,63563,63511],{"class":1436},[1086,63565,4340],{"class":4109},[1086,63567,63568],{"class":1436},"Math",[1086,63570,861],{"class":1146},[1086,63572,63573],{"class":1105},"floor",[1086,63575,1398],{"class":4109},[1086,63577,63568],{"class":1436},[1086,63579,861],{"class":1146},[1086,63581,63582],{"class":1105},"random",[1086,63584,49217],{"class":4109},[1086,63586,2775],{"class":1146},[1086,63588,63511],{"class":1436},[1086,63590,861],{"class":1146},[1086,63592,38671],{"class":1436},[1086,63594,63595],{"class":4109},")]\n",[1086,63597,63598],{"class":1088,"line":61734},[1086,63599,1285],{"class":1146},[1086,63601,63602],{"class":1088,"line":61739},[1086,63603,1291],{"class":1146},[1086,63605,63606],{"class":1088,"line":61753},[1086,63607,3390],{"emptyLinePlaceholder":738},[1086,63609,63610,63612,63615,63618,63620,63622,63624],{"class":1088,"line":61772},[1086,63611,3625],{"class":1423},[1086,63613,63614],{"class":1155}," const",[1086,63616,63617],{"class":1436}," musicService ",[1086,63619,1440],{"class":1146},[1086,63621,26591],{"class":1146},[1086,63623,62632],{"class":1105},[1086,63625,1387],{"class":1436},[4937,63627],{},[863,63629,63631],{"id":63630},"advanced-tips-tricks","Advanced Tips & Tricks",[1074,63633,63635],{"id":63634},"tip-1-be-specific-with-context","Tip 1: Be Specific with Context",[842,63637,63638],{},"The more context you provide, the better results you'll get:",[1013,63640,63643],{"className":63641,"code":63642,"language":1018},[1016],"\"Find music for my game\"\n\n\"Find calm, ambient music for the menu screen of a puzzle game\n targeted at adults. Should feel modern and minimalist,\n around 70-90 BPM, with soft synths or piano.\"\n",[895,63644,63642],{"__ignoreMap":728},[1074,63646,63648],{"id":63647},"tip-2-use-mood-combinations","Tip 2: Use Mood Combinations",[842,63650,63651],{},"Combine multiple mood descriptors for nuanced results:",[1013,63653,63656],{"className":63654,"code":63655,"language":1018},[1016],"\"Find tracks that are both mysterious AND playful -\nlike exploring a whimsical haunted house\"\n",[895,63657,63655],{"__ignoreMap":728},[1074,63659,63661],{"id":63660},"tip-3-request-alternatives","Tip 3: Request Alternatives",[842,63663,63664],{},"Ask for variations to build a cohesive soundtrack:",[1013,63666,63669],{"className":63667,"code":63668,"language":1018},[1016],"\"I like this track. Can you find 5 similar tracks\nthat would work well together as a playlist?\"\n",[895,63670,63668],{"__ignoreMap":728},[1074,63672,63674],{"id":63673},"tip-4-specify-technical-requirements","Tip 4: Specify Technical Requirements",[842,63676,63677],{},"Include technical specs when needed:",[1013,63679,63682],{"className":63680,"code":63681,"language":1018},[1016],"\"Find tracks that loop seamlessly, are exactly 30 seconds long,\nand have no vocals - suitable for background gameplay music\"\n",[895,63683,63681],{"__ignoreMap":728},[4937,63685],{},[863,63687,63689],{"id":63688},"licensing-usage-rights","Licensing & Usage Rights",[842,63691,63692],{},"When using Epidemic Sound tracks in your projects, remember:",[871,63694,63695,63704],{},[874,63696,63697],{},[877,63698,63699,63701],{},[880,63700,31852],{},[880,63702,63703],{},"License Required",[887,63705,63706,63714,63722,63730],{},[877,63707,63708,63711],{},[892,63709,63710],{},"Personal projects",[892,63712,63713],{},"Personal subscription",[877,63715,63716,63719],{},[892,63717,63718],{},"Commercial games",[892,63720,63721],{},"Commercial license",[877,63723,63724,63727],{},[892,63725,63726],{},"YouTube/Streaming",[892,63728,63729],{},"Creator subscription",[877,63731,63732,63735],{},[892,63733,63734],{},"Client work",[892,63736,63721],{},[842,63738,63739,63740,63745],{},"Always verify your license covers your intended use case. Visit ",[846,63741,63744],{"href":63742,"rel":63743},"https://www.epidemicsound.com/pricing/",[850],"Epidemic Sound Licensing"," for details.",[4937,63747],{},[863,63749,63751],{"id":63750},"troubleshooting","Troubleshooting",[1074,63753,63755],{"id":63754},"mcp-server-not-connecting","MCP Server Not Connecting",[1013,63757,63759],{"className":1080,"code":63758,"language":1082,"meta":728,"style":728},"# Debug MCP connections\nclaude --mcp-debug\n\n# Check if the server is properly configured\nclaude mcp list\n",[895,63760,63761,63766,63773,63777,63782],{"__ignoreMap":728},[1086,63762,63763],{"class":1088,"line":1089},[1086,63764,63765],{"class":1427},"# Debug MCP connections\n",[1086,63767,63768,63770],{"class":1088,"line":729},[1086,63769,6485],{"class":1092},[1086,63771,63772],{"class":1096}," --mcp-debug\n",[1086,63774,63775],{"class":1088,"line":1112},[1086,63776,3390],{"emptyLinePlaceholder":738},[1086,63778,63779],{"class":1088,"line":1181},[1086,63780,63781],{"class":1427},"# Check if the server is properly configured\n",[1086,63783,63784,63786,63788],{"class":1088,"line":1205},[1086,63785,6485],{"class":1092},[1086,63787,8553],{"class":1096},[1086,63789,59890],{"class":1096},[1074,63791,63793],{"id":63792},"authentication-issues","Authentication Issues",[991,63795,63796,63799,63802],{},[961,63797,63798],{},"Clear cached credentials",[961,63800,63801],{},"Re-authenticate via OAuth",[961,63803,63804],{},"Check your Epidemic Sound subscription status",[1074,63806,63808],{"id":63807},"no-results-returned","No Results Returned",[958,63810,63811,63814,63817],{},[961,63812,63813],{},"Verify your search terms aren't too restrictive",[961,63815,63816],{},"Check your internet connection",[961,63818,63819],{},"Ensure Epidemic Sound API is accessible",[4937,63821],{},[863,63823,18681],{"id":18680},[842,63825,63826],{},"The Epidemic Sound MCP integration with Claude represents a significant leap forward in creative workflows. By combining AI-powered natural language search with one of the world's largest royalty-free music libraries, developers and creators can:",[958,63828,63829,63835,63841,63846],{},[961,63830,63831,63834],{},[996,63832,63833],{},"Save hours"," previously spent browsing catalogs",[961,63836,63837,63840],{},[996,63838,63839],{},"Discover music"," that perfectly matches their creative vision",[961,63842,63843,63845],{},[996,63844,8806],{}," without context-switching between tools",[961,63847,63848,63851],{},[996,63849,63850],{},"Build better products"," with professionally curated audio",[842,63853,63854],{},"Whether you're building games, creating content, or developing interactive experiences, this integration streamlines the entire music discovery process.",[4937,63856],{},[863,63858,18693],{"id":18692},[958,63860,63861,63867,63873,63879,63886],{},[961,63862,63863],{},[846,63864,63866],{"href":43168,"rel":63865},[850],"Epidemic Sound MCP Documentation",[961,63868,63869],{},[846,63870,63872],{"href":1675,"rel":63871},[850],"Model Context Protocol Specification",[961,63874,63875],{},[846,63876,63878],{"href":59505,"rel":63877},[850],"Claude Desktop Download",[961,63880,63881],{},[846,63882,63885],{"href":63883,"rel":63884},"https://www.epidemicsound.com/developers/",[850],"Epidemic Sound API Documentation",[961,63887,63888],{},[846,63889,63891],{"href":12838,"rel":63890},[850],"MusicTech Lab Projects",[1680,63893,63894],{},"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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":728,"searchDepth":729,"depth":729,"links":63896},[63897,63898,63901,63904,63905,63913,63918,63923,63929,63930,63935,63936],{"id":27939,"depth":729,"text":27940},{"id":59409,"depth":729,"text":59410,"children":63899},[63900],{"id":59416,"depth":1112,"text":59417},{"id":59448,"depth":729,"text":59449,"children":63902},[63903],{"id":59469,"depth":1112,"text":59470},{"id":15347,"depth":729,"text":15348},{"id":59529,"depth":729,"text":59530,"children":63906},[63907,63908,63909,63910,63911,63912],{"id":59533,"depth":1112,"text":59534},{"id":59568,"depth":1112,"text":59569},{"id":59629,"depth":1112,"text":59630},{"id":59849,"depth":1112,"text":59850},{"id":59870,"depth":1112,"text":59871},{"id":59917,"depth":1112,"text":59918},{"id":59941,"depth":729,"text":59942,"children":63914},[63915,63916,63917],{"id":59950,"depth":1112,"text":59951},{"id":59963,"depth":1112,"text":59964},{"id":59973,"depth":1112,"text":59974},{"id":59985,"depth":729,"text":59986,"children":63919},[63920,63921,63922],{"id":59989,"depth":1112,"text":59990},{"id":60708,"depth":1112,"text":60709},{"id":62484,"depth":1112,"text":62485},{"id":63630,"depth":729,"text":63631,"children":63924},[63925,63926,63927,63928],{"id":63634,"depth":1112,"text":63635},{"id":63647,"depth":1112,"text":63648},{"id":63660,"depth":1112,"text":63661},{"id":63673,"depth":1112,"text":63674},{"id":63688,"depth":729,"text":63689},{"id":63750,"depth":729,"text":63751,"children":63931},[63932,63933,63934],{"id":63754,"depth":1112,"text":63755},{"id":63792,"depth":1112,"text":63793},{"id":63807,"depth":1112,"text":63808},{"id":18680,"depth":729,"text":18681},{"id":18692,"depth":729,"text":18693},"2025-01-15T00:00:00.000Z","Set up and use the Epidemic Sound MCP Server with Claude to discover music using natural language prompts directly in your development workflow.",{"src":63940},"/images/blog/musictechlab_blog_epidemic-sound-mcp-claude.webp",{"enabled":738,"items":63942},[63943,63945,63947],{"text":63944,"icon":7560},"Epidemic Sound MCP lets you search 50,000+ tracks using natural language inside Claude.",{"text":63946,"icon":1723},"MCP keeps everything local; no web browser or mobile app required.",{"text":63948,"icon":9547},"Contextual prompts with mood, BPM, and genre produce significantly better search results.",{},{"title":148,"description":63938},[14100,5523,11843,5519],"KDvEqFq8Iz_aTwWadEJ4r0z9ftaGNplu0zXENXU5euc",{"id":63954,"title":14,"authors":723,"badge":63955,"body":63956,"category":4990,"client":64295,"date":64296,"description":64297,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":64298,"keyTakeaways":64300,"meta":64310,"navigation":738,"path":15,"seo":64311,"status":723,"stem":16,"tags":64312,"teaser":723,"__hash__":64313},"posts/blog/case-study/chromecast-airplay-casting-app-case-study.md",{"label":5,"color":50099},{"type":725,"value":63957,"toc":64282},[63958,63962,63965,63968,63971,63976,63980,63987,63990,64012,64015,64019,64022,64025,64040,64045,64049,64052,64055,64059,64062,64068,64071,64075,64091,64095,64126,64130,64133,64150,64155,64159,64223,64225,64260,64264],[863,63959,63961],{"id":63960},"the-brief-that-seemed-simple","The Brief That Seemed Simple",[842,63963,63964],{},"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.\"",[842,63966,63967],{},"Simple, right? Tap a video, tap a TV, watch.",[842,63969,63970],{},"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.",[1572,63972,63973],{},[842,63974,63975],{},"We quickly realized this wasn't a development project. It was an R&D expedition.",[863,63977,63979],{"id":63978},"the-real-problem-control-not-just-playback","The Real Problem: Control, Not Just Playback",[842,63981,63982,63983,63986],{},"Here's what most people don't understand about Chromecast: playing a video is easy. ",[964,63984,63985],{},"Controlling"," what happens on the TV is hard.",[842,63988,63989],{},"Chromecast was designed for a simple use case — send a URL, let the TV handle it. But our client needed more:",[1045,63991,63993,63998,64003,64007],{"className":63992},[1048,1049,1765,1051,1052],[1054,63994],{"description":63995,"icon":63996,"title":63997},"Toggle QR codes on and off during playback","i-lucide-qr-code","QR Code Overlays",[1054,63999],{"description":64000,"icon":64001,"title":64002},"Show debug panels for testing","i-lucide-bug","Debug Information",[1054,64004],{"description":64005,"icon":1769,"title":64006},"Display graphics at specific timestamps","Custom Overlays",[1054,64008],{"description":64009,"icon":64010,"title":64011},"Send commands the TV actually understands","i-lucide-send","Custom Commands",[842,64013,64014],{},"The default Chromecast receiver doesn't do any of that. We needed to build our own.",[863,64016,64018],{"id":64017},"the-platform-decision","The Platform Decision",[842,64020,64021],{},"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?",[842,64023,64024],{},"We built proof-of-concept apps in three different frameworks:",[1045,64026,64028,64033,64037],{"className":64027},[1048,1049,1050,1051,1052],[1054,64029],{"description":64030,"icon":64031,"title":64032},"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",[1054,64034],{"description":64035,"icon":64031,"title":64036},"Best casting integration, but maintaining two codebases for a startup budget wasn't realistic. More time syncing features than building them.","Native Swift/Kotlin",[1054,64038],{"description":64039,"icon":10530,"title":47091},"Google's backing meant solid Chromecast support. Plugin architecture let us write native casting code when needed, keeping 90% cross-platform.",[1032,64041,64042],{},[842,64043,64044],{},"We went with Flutter. It was a bet, but one that paid off.",[863,64046,64048],{"id":64047},"the-casting-puzzle-14-prototypes-later","The Casting Puzzle: 14 Prototypes Later",[842,64050,64051],{},"Here's something we don't usually admit in case studies: we built fourteen different prototypes before landing on the final architecture.",[3572,64053],{":items":64054},"[{\"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\"}]",[863,64056,64058],{"id":64057},"the-airplay-surprise","The AirPlay Surprise",[842,64060,64061],{},"AirPlay should have been easier — Apple's ecosystem, tight integration, \"it just works.\"",[842,64063,64064,64065],{},"Except Flutter had no official AirPlay plugin. ",[996,64066,64067],{},"We wrote our own.",[842,64069,64070],{},"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.",[863,64072,64074],{"id":64073},"the-problems-nobody-warned-us-about","The Problems Nobody Warned Us About",[1045,64076,64078,64082,64086],{"className":64077},[1048,1049,1050,1051,1052],[1054,64079],{"description":64080,"icon":3897,"title":64081},"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.","Network Discovery is Chaos",[1054,64083],{"description":64084,"icon":52268,"title":64085},"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",[1054,64087],{"description":64088,"icon":64089,"title":64090},"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",[863,64092,64094],{"id":64093},"what-we-actually-built","What We Actually Built",[1045,64096,64098,64103,64108,64112,64117,64122],{"className":64097},[1048,1049,1765,1051,1052],[1054,64099],{"description":64100,"icon":64101,"title":64102},"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",[1054,64104],{"description":64105,"icon":64106,"title":64107},"Chromecast and AirPlay from the same interface. Users don't need to know which protocol their TV uses.","i-lucide-airplay","Universal Casting",[1054,64109],{"description":64110,"icon":1769,"title":64111},"QR codes, logos, and animations toggled instantly. No re-encoding, no delay.","Real-Time Overlay Control",[1054,64113],{"description":64114,"icon":64115,"title":64116},"Content organized into channels with D-pad navigation. Swipe up for next channel, swipe right for next video.","i-lucide-tv","Channel-Based Browsing",[1054,64118],{"description":64119,"icon":64120,"title":64121},"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",[1054,64123],{"description":64124,"icon":64001,"title":64125},"Toggle a debug panel on the TV from your phone. Essential for testing, easy to disable in production.","Debug Mode",[863,64127,64129],{"id":64128},"the-rd-investment","The R&D Investment",[842,64131,64132],{},"Fourteen prototypes sounds like waste. It wasn't. Each failed approach taught us something:",[958,64134,64135,64138,64141,64144,64147],{},[961,64136,64137],{},"WebRTC showed us the value of QR pairing",[961,64139,64140],{},"FFmpeg experiments proved client-side rendering was necessary",[961,64142,64143],{},"React Native's limitations confirmed Flutter was the right choice",[961,64145,64146],{},"The multi-layer PoC became the foundation of our overlay system",[961,64148,64149],{},"Early Chromecast tests revealed we needed a custom receiver",[1572,64151,64152],{},[842,64153,64154],{},"The client got a production app, but they also got certainty. We didn't guess at the architecture — we proved it through systematic exploration.",[863,64156,64158],{"id":64157},"lessons-for-clients","Lessons for Clients",[871,64160,64161,64171],{},[874,64162,64163],{},[877,64164,64165,64168],{},[880,64166,64167],{},"Insight",[880,64169,64170],{},"Why it matters",[887,64172,64173,64183,64193,64203,64213],{},[877,64174,64175,64180],{},[892,64176,64177],{},[996,64178,64179],{},"Casting isn't streaming",[892,64181,64182],{},"Sending video to a TV is easy. Controlling what happens on that TV requires custom development",[877,64184,64185,64190],{},[892,64186,64187],{},[996,64188,64189],{},"Custom receivers unlock everything",[892,64191,64192],{},"Anything beyond play/pause/seek means building your own Chromecast receiver",[877,64194,64195,64200],{},[892,64196,64197],{},[996,64198,64199],{},"Platform choice matters enormously",[892,64201,64202],{},"Two weeks on React Native felt like lost time, but it would have been months of pain",[877,64204,64205,64210],{},[892,64206,64207],{},[996,64208,64209],{},"Prototypes aren't waste",[892,64211,64212],{},"Every PoC either became part of the final product or eliminated a dead end early",[877,64214,64215,64220],{},[892,64216,64217],{},[996,64218,64219],{},"Test on real hardware",[892,64221,64222],{},"Chromecast bugs don't show up in simulators. We maintained a \"casting corner\" with multiple device generations",[863,64224,8484],{"id":8483},[1045,64226,64228,64231,64235,64239,64244,64249,64253,64256],{"className":64227},[1048,50574,1050,50642,50239,1052],[1054,64229],{"description":64230,"icon":4855,"title":47091},"Cross-platform mobile",[1054,64232],{"description":64233,"icon":64101,"title":64234},"Custom receiver app","Chromecast SDK",[1054,64236],{"description":64237,"icon":64106,"title":64238},"Custom Flutter plugin","AirPlay",[1054,64240],{"description":64241,"icon":64242,"title":64243},"QR-based device pairing","i-lucide-video","WebRTC",[1054,64245],{"description":64246,"icon":64247,"title":64248},"Video processing","i-lucide-film","FFmpeg",[1054,64250],{"description":64251,"icon":12409,"title":64252},"Animation overlays","Lottie",[1054,64254],{"description":64255,"icon":6395,"title":59489},"Signaling server",[1054,64257],{"description":64258,"icon":2939,"title":64259},"Video processing API","FastAPI",[863,64261,64263],{"id":64262},"deliverables","Deliverables",[1045,64265,64267,64271,64274,64278],{"className":64266},[1048,1049,1765,1051,1052],[1054,64268],{"description":64269,"icon":4855,"title":64270},"iOS + Android from a single Flutter codebase","Cross-Platform Mobile App",[1054,64272],{"description":64273,"icon":64101,"title":64102},"With overlay support and custom messaging protocol",[1054,64275],{"description":64276,"icon":64106,"title":64277},"Built from scratch for Flutter","Custom AirPlay Plugin",[1054,64279],{"description":64280,"icon":7546,"title":64281},"Content and overlay management","Backend CMS",{"title":728,"searchDepth":729,"depth":729,"links":64283},[64284,64285,64286,64287,64288,64289,64290,64291,64292,64293,64294],{"id":63960,"depth":729,"text":63961},{"id":63978,"depth":729,"text":63979},{"id":64017,"depth":729,"text":64018},{"id":64047,"depth":729,"text":64048},{"id":64057,"depth":729,"text":64058},{"id":64073,"depth":729,"text":64074},{"id":64093,"depth":729,"text":64094},{"id":64128,"depth":729,"text":64129},{"id":64157,"depth":729,"text":64158},{"id":8483,"depth":729,"text":8484},{"id":64262,"depth":729,"text":64263},{"nda":738},"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":64299,"hasLogo":69},"/images/blog/chromecast-airplay-casting-app-case-study.webp",{"enabled":738,"items":64301},[64302,64304,64306,64308],{"text":64303,"icon":8737},"14 prototypes built before finding the right casting architecture.",{"text":64305,"icon":64101},"Custom Chromecast receiver enables real-time QR code and animation overlays on TV.",{"text":64307,"icon":5365},"Custom AirPlay Flutter plugin written from scratch in 3 weeks.",{"text":64309,"icon":4855},"Flutter chosen over React Native and native after PoC comparison across all three.",{},{"title":14,"description":64297},[4990,18784],"rp1YICin9SG9v9J2bLejUif7lpAB5aZe85kWeFkRR6A",{"id":64315,"title":38,"authors":723,"badge":64316,"body":64317,"category":4990,"client":65615,"date":64296,"description":65618,"extension":734,"faq":723,"featured":738,"featuredOrder":3456,"hidden":69,"image":65619,"keyTakeaways":65621,"meta":65631,"navigation":738,"path":39,"seo":65632,"status":723,"stem":40,"tags":65633,"teaser":723,"__hash__":65634},"posts/blog/case-study/musicdata-lab-universal-music-data-parser-case-study.md",{"label":5,"color":50099},{"type":725,"value":64318,"toc":65581},[64319,64324,64328,64337,64340,64345,64356,64359,64362,64366,64369,64455,64458,64462,64465,64469,64472,64475,64479,64482,64499,64501,64504,64518,64650,64659,64668,64672,64679,64683,64787,64791,64879,64883,64909,64913,64916,64999,65008,65017,65021,65047,65051,65054,65074,65083,65087,65135,65138,65163,65188,65192,65195,65257,65266,65270,65274,65281,65349,65353,65356,65362,65367,65376,65380,65383,65387,65412,65416,65419,65450,65459,65468,65477,65481,65484,65503,65510,65513,65515,65543,65545,65548,65578],[1572,64320,64321],{},[842,64322,64323],{},"This case study describes our work with CherryRed Records. Some technical details have been generalized to protect proprietary systems.",[863,64325,64327],{"id":64326},"the-problem-data-chaos-in-music-industry","The Problem: Data Chaos in Music Industry",[1045,64329,64331],{"className":64330},[13033,13034,1052],[842,64332,64333],{},[1027,64334],{"alt":64335,"src":64336,"width":30896},"MusicData Lab - login page with CherryRed Records branding","/images/blog/musicdatalab/login.webp",[842,64338,64339],{},"It was 2023 when we first sat down with a UK music label facing a common but frustrating problem. Every month, they received royalty reports from their distributors - ADA (Warner), The Orchard (Sony), Merlin, FUGA, Bandcamp, and a dozen others. Each report contained the same basic information: how many times their artists' songs were streamed, and how much money they earned.",[1901,64341,64342],{},[842,64343,64344],{},"Simple, right? Not quite.",[842,64346,64347,64349,64350,64352,64353,64355],{},[996,64348,12951],{}," sent Excel files with columns named \"Release Artist\", \"Release Title\", and \"Net Payable\". ",[996,64351,13274],{}," called them \"Artist name\", \"Track name\", and \"Payable\". ",[996,64354,12971],{}," went with \"Artist Name\", \"Track Name\", and \"Label Share Net Receipts\".",[842,64357,64358],{},"Same data. Different languages.",[842,64360,64361],{},"And that was just the beginning.",[1074,64363,64365],{"id":64364},"the-date-format-nightmare","The Date Format Nightmare",[842,64367,64368],{},"Consider how different distributors format dates:",[871,64370,64371,64382],{},[874,64372,64373],{},[877,64374,64375,64377,64380],{},[880,64376,10403],{},[880,64378,64379],{},"Date Format",[880,64381,7741],{},[887,64383,64384,64396,64409,64421,64431,64443],{},[877,64385,64386,64388,64391],{},[892,64387,12951],{},[892,64389,64390],{},"Excel serial number",[892,64392,64393],{},[895,64394,64395],{},"45292",[877,64397,64398,64401,64404],{},[892,64399,64400],{},"ADA (variant)",[892,64402,64403],{},"YYYYMM integer",[892,64405,64406],{},[895,64407,64408],{},"202501",[877,64410,64411,64413,64416],{},[892,64412,12971],{},[892,64414,64415],{},"\"YYYYMNN\"",[892,64417,64418],{},[895,64419,64420],{},"\"2025M01\"",[877,64422,64423,64425,64428],{},[892,64424,13274],{},[892,64426,64427],{},"Hidden in metadata cells",[892,64429,64430],{},"Row 1, Column B",[877,64432,64433,64435,64438],{},[892,64434,14327],{},[892,64436,64437],{},"Embedded in filename",[892,64439,64440],{},[895,64441,64442],{},"Qello_20250101_20250331.xlsx",[877,64444,64445,64447,64450],{},[892,64446,56369],{},[892,64448,64449],{},"ISO date string",[892,64451,64452],{},[895,64453,64454],{},"\"2025-01-15\"",[842,64456,64457],{},"One distributor. Four date formats. And we had 20+ distributors to support.",[1074,64459,64461],{"id":64460},"the-currency-problem","The Currency Problem",[842,64463,64464],{},"Some distributors report in USD. Others in EUR, GBP, or the original local currency. Some files contain mixed currencies. Some hardcode \"USD\" even when the actual currency varies by territory.",[1074,64466,64468],{"id":64467},"the-same-but-different-problem","The \"Same But Different\" Problem",[842,64470,64471],{},"\"UNITED STATES\" vs \"USA\" vs \"US\" vs \"United States of America\". All the same country. All different strings that need to be normalized.",[842,64473,64474],{},"\"Sony Music\" vs \"SONY\" vs \"Sony Music Entertainment\" vs \"SME\". Same label. Four different spellings across different platforms.",[863,64476,64478],{"id":64477},"the-solution-adapter-pattern-with-field-mapping","The Solution: Adapter Pattern with Field Mapping",[842,64480,64481],{},"We needed a system that could:",[991,64483,64484,64487,64490,64493,64496],{},[961,64485,64486],{},"Accept any file format (CSV, XLS, XLSX, XLSB)",[961,64488,64489],{},"Map arbitrary column names to a standard schema",[961,64491,64492],{},"Handle date parsing edge cases per distributor",[961,64494,64495],{},"Normalize currencies, countries, and entity names",[961,64497,64498],{},"Scale to hundreds of millions of records",[1074,64500,26146],{"id":26145},[842,64502,64503],{},"At its core, the system uses the Adapter Pattern - a universal base class that handles all common operations, with specialized adapters for each distributor that define only what's different:",[958,64505,64506,64512],{},[961,64507,64508,64511],{},[996,64509,64510],{},"Base MDLImporter"," - handles file parsing, chunked processing, country normalization, currency conversion, and Elasticsearch indexing",[961,64513,64514,64517],{},[996,64515,64516],{},"Distributor Adapters"," - each adapter contains only a field mapping dictionary and any date/currency parsing overrides specific to that distributor",[1013,64519,64522],{"className":1368,"code":64520,"filename":64521,"language":1250,"meta":728,"style":728},"class ADAAdapter(BaseMDLImporter):\n    FIELD_MAP = {\n        \"Release Artist\": \"artist\",\n        \"Release Title\":  \"track\",\n        \"Net Payable\":    \"income\",\n    }\n\n    def parse_date(self, raw):\n        # ADA uses Excel serial numbers\n        return excel_serial_to_date(raw)\n","adapters/ada_adapter.py",[895,64523,64524,64538,64547,64566,64585,64605,64609,64613,64631,64636],{"__ignoreMap":728},[1086,64525,64526,64528,64531,64533,64536],{"class":1088,"line":1089},[1086,64527,4036],{"class":1155},[1086,64529,64530],{"class":1092}," ADAAdapter",[1086,64532,1398],{"class":1146},[1086,64534,64535],{"class":1092},"BaseMDLImporter",[1086,64537,4047],{"class":1146},[1086,64539,64540,64543,64545],{"class":1088,"line":729},[1086,64541,64542],{"class":1436},"    FIELD_MAP ",[1086,64544,1440],{"class":1146},[1086,64546,1164],{"class":1146},[1086,64548,64549,64551,64554,64556,64558,64560,64562,64564],{"class":1088,"line":1112},[1086,64550,6046],{"class":1146},[1086,64552,64553],{"class":1096},"Release Artist",[1086,64555,1159],{"class":1146},[1086,64557,1133],{"class":1146},[1086,64559,1195],{"class":1146},[1086,64561,7377],{"class":1096},[1086,64563,1159],{"class":1146},[1086,64565,1202],{"class":1146},[1086,64567,64568,64570,64573,64575,64577,64579,64581,64583],{"class":1088,"line":1181},[1086,64569,6046],{"class":1146},[1086,64571,64572],{"class":1096},"Release Title",[1086,64574,1159],{"class":1146},[1086,64576,1133],{"class":1146},[1086,64578,1152],{"class":1146},[1086,64580,33099],{"class":1096},[1086,64582,1159],{"class":1146},[1086,64584,1202],{"class":1146},[1086,64586,64587,64589,64592,64594,64596,64598,64601,64603],{"class":1088,"line":1205},[1086,64588,6046],{"class":1146},[1086,64590,64591],{"class":1096},"Net Payable",[1086,64593,1159],{"class":1146},[1086,64595,1133],{"class":1146},[1086,64597,1169],{"class":1146},[1086,64599,64600],{"class":1096},"income",[1086,64602,1159],{"class":1146},[1086,64604,1202],{"class":1146},[1086,64606,64607],{"class":1088,"line":1276},[1086,64608,1279],{"class":1146},[1086,64610,64611],{"class":1088,"line":1282},[1086,64612,3390],{"emptyLinePlaceholder":738},[1086,64614,64615,64617,64620,64622,64624,64626,64629],{"class":1088,"line":1288},[1086,64616,4065],{"class":1155},[1086,64618,64619],{"class":1105}," parse_date",[1086,64621,1398],{"class":1146},[1086,64623,4074],{"class":4073},[1086,64625,1227],{"class":1146},[1086,64627,64628],{"class":1401}," raw",[1086,64630,4047],{"class":1146},[1086,64632,64633],{"class":1088,"line":2685},[1086,64634,64635],{"class":1427},"        # ADA uses Excel serial numbers\n",[1086,64637,64638,64640,64643,64645,64648],{"class":1088,"line":2700},[1086,64639,4239],{"class":1423},[1086,64641,64642],{"class":1105}," excel_serial_to_date",[1086,64644,1398],{"class":1146},[1086,64646,64647],{"class":1105},"raw",[1086,64649,1455],{"class":1146},[1032,64651,64652],{},[842,64653,64654,64655,64658],{},"This approach means adding a new distributor typically takes just ",[996,64656,64657],{},"2-3 hours"," of work - define the field mapping, handle any date/currency quirks, and you're done.",[1045,64660,64662],{"className":64661},[13033,13034,1052],[842,64663,64664],{},[1027,64665],{"alt":64666,"src":64667,"width":30896},"Adapter configuration - field mapping between distributor columns and the universal schema","/images/blog/musicdatalab/adapters_conf.webp",[863,64669,64671],{"id":64670},"supported-distributors-and-adapters","Supported Distributors and Adapters",[842,64673,64674,64675,64678],{},"We currently support ",[996,64676,64677],{},"25+ adapters"," covering the major music distribution ecosystem:",[1074,64680,64682],{"id":64681},"major-distributors","Major Distributors",[871,64684,64685,64697],{},[874,64686,64687],{},[877,64688,64689,64691,64694],{},[880,64690,10403],{},[880,64692,64693],{},"Parent Company",[880,64695,64696],{},"Adapter Variants",[887,64698,64699,64711,64722,64734,64746,64756,64767,64777],{},[877,64700,64701,64705,64708],{},[892,64702,64703],{},[996,64704,12951],{},[892,64706,64707],{},"Warner Music",[892,64709,64710],{},"5 adapters (Standard, V2, V2Fix, XLS, Custom)",[877,64712,64713,64717,64719],{},[892,64714,64715],{},[996,64716,12971],{},[892,64718,52889],{},[892,64720,64721],{},"2 adapters (Standard, Legacy)",[877,64723,64724,64728,64731],{},[892,64725,64726],{},[996,64727,13274],{},[892,64729,64730],{},"Independent",[892,64732,64733],{},"2 adapters (Standard, Historical)",[877,64735,64736,64740,64743],{},[892,64737,64738],{},[996,64739,12942],{},[892,64741,64742],{},"Downtown Music",[892,64744,64745],{},"1 adapter",[877,64747,64748,64752,64754],{},[892,64749,64750],{},[996,64751,56369],{},[892,64753,64730],{},[892,64755,64721],{},[877,64757,64758,64763,64765],{},[892,64759,64760],{},[996,64761,64762],{},"TuneCore",[892,64764,56369],{},[892,64766,64745],{},[877,64768,64769,64773,64775],{},[892,64770,64771],{},[996,64772,10406],{},[892,64774,64730],{},[892,64776,64745],{},[877,64778,64779,64783,64785],{},[892,64780,64781],{},[996,64782,57006],{},[892,64784,64742],{},[892,64786,64745],{},[1074,64788,64790],{"id":64789},"specialized-platforms","Specialized Platforms",[871,64792,64793,64803],{},[874,64794,64795],{},[877,64796,64797,64799,64801],{},[880,64798,5024],{},[880,64800,8043],{},[880,64802,2719],{},[887,64804,64805,64817,64829,64842,64853,64866],{},[877,64806,64807,64811,64814],{},[892,64808,64809],{},[996,64810,8748],{},[892,64812,64813],{},"Direct-to-fan",[892,64815,64816],{},"Custom CSV format",[877,64818,64819,64823,64826],{},[892,64820,64821],{},[996,64822,14327],{},[892,64824,64825],{},"Concert streaming",[892,64827,64828],{},"Date embedded in filename",[877,64830,64831,64836,64839],{},[892,64832,64833],{},[996,64834,64835],{},"Soundcloud",[892,64837,64838],{},"Streaming",[892,64840,64841],{},"Multiple report types",[877,64843,64844,64848,64850],{},[892,64845,64846],{},[996,64847,5081],{},[892,64849,64838],{},[892,64851,64852],{},"Content ID integration",[877,64854,64855,64860,64863],{},[892,64856,64857],{},[996,64858,64859],{},"TikTok",[892,64861,64862],{},"Social/UGC",[892,64864,64865],{},"Emerging format",[877,64867,64868,64873,64876],{},[892,64869,64870],{},[996,64871,64872],{},"Meta (Facebook/Instagram)",[892,64874,64875],{},"Social",[892,64877,64878],{},"Rights manager exports",[1074,64880,64882],{"id":64881},"regional-distributors","Regional Distributors",[958,64884,64885,64891,64897,64903],{},[961,64886,64887,64890],{},[996,64888,64889],{},"Phonofile"," (Nordic region)",[961,64892,64893,64896],{},[996,64894,64895],{},"Zebralution"," (Germany)",[961,64898,64899,64902],{},[996,64900,64901],{},"IDOL"," (France)",[961,64904,64905,64908],{},[996,64906,64907],{},"Altafonte"," (Spain/Latin America)",[863,64910,64912],{"id":64911},"supported-file-formats","Supported File Formats",[842,64914,64915],{},"The system handles every file format encountered in the music distribution industry:",[871,64917,64918,64931],{},[874,64919,64920],{},[877,64921,64922,64924,64927,64929],{},[880,64923,14141],{},[880,64925,64926],{},"Extension",[880,64928,31852],{},[880,64930,2719],{},[887,64932,64933,64946,64959,64972,64985],{},[877,64934,64935,64938,64940,64943],{},[892,64936,64937],{},"CSV",[892,64939,14188],{},[892,64941,64942],{},"Most common",[892,64944,64945],{},"UTF-8 and ISO-8859-1 encoding support",[877,64947,64948,64951,64953,64956],{},[892,64949,64950],{},"Excel",[892,64952,14158],{},[892,64954,64955],{},"Standard Excel",[892,64957,64958],{},"openpyxl with read-only mode",[877,64960,64961,64964,64966,64969],{},[892,64962,64963],{},"Excel Binary",[892,64965,14173],{},[892,64967,64968],{},"Large files",[892,64970,64971],{},"pyxlsb parser, common for 500K+ rows",[877,64973,64974,64977,64979,64982],{},[892,64975,64976],{},"Legacy Excel",[892,64978,14203],{},[892,64980,64981],{},"Older systems",[892,64983,64984],{},"xlrd parser",[877,64986,64987,64990,64993,64996],{},[892,64988,64989],{},"TSV",[892,64991,64992],{},".tsv",[892,64994,64995],{},"Some APIs",[892,64997,64998],{},"Tab-separated values",[1045,65000,65002],{"className":65001},[13033,13034,1052],[842,65003,65004],{},[1027,65005],{"alt":65006,"src":65007,"width":30896},"File uploader - importing distributor royalty files with status tracking","/images/blog/musicdatalab/files_uploader.webp",[1045,65009,65011],{"className":65010},[13033,13034,1052],[842,65012,65013],{},[1027,65014],{"alt":65015,"src":65016,"width":30896},"Example files - sample distributor reports for testing imports and demos","/images/blog/musicdatalab/example-files.webp",[1074,65018,65020],{"id":65019},"file-format-quirks-we-handle","File Format Quirks We Handle",[958,65022,65023,65029,65035,65041],{},[961,65024,65025,65028],{},[996,65026,65027],{},"XLS files containing CSV data"," - The Orchard and ADA sometimes send .xls files that are actually CSV",[961,65030,65031,65034],{},[996,65032,65033],{},"Mixed encodings"," - Automatic fallback from UTF-8 to ISO-8859-1",[961,65036,65037,65040],{},[996,65038,65039],{},"Files with metadata headers"," - Skip non-data rows (Merlin stores dates in row 1)",[961,65042,65043,65046],{},[996,65044,65045],{},"Multi-sheet workbooks"," - Automatic sheet detection",[863,65048,65050],{"id":65049},"scale-200-million-records-and-growing","Scale: 200 Million Records and Growing",[842,65052,65053],{},"Our production system currently manages:",[1045,65055,65058,65062,65066,65070],{"className":65056},[1048,50574,1051,1052,65057],"text-center",[1054,65059],{"description":65060,"title":65061},"Streaming records","200M+",[1054,65063],{"description":65064,"title":65065},"Active adapters","25+",[1054,65067],{"description":65068,"title":65069},"File format variations","50+",[1054,65071],{"description":65072,"title":65073},"New records/month","5-10M",[1045,65075,65077],{"className":65076},[13033,13034,1052],[842,65078,65079],{},[1027,65080],{"alt":65081,"src":65082,"width":30896},"Main dashboard - real-time overview of streams, adapters, and income by retailer and artist","/images/blog/musicdatalab/dashboard.webp",[1074,65084,65086],{"id":65085},"performance-metrics","Performance Metrics",[871,65088,65089,65098],{},[874,65090,65091],{},[877,65092,65093,65096],{},[880,65094,65095],{},"Operation",[880,65097,42650],{},[887,65099,65100,65107,65119,65127],{},[877,65101,65102,65105],{},[892,65103,65104],{},"Single file import (500K rows)",[892,65106,48738],{},[877,65108,65109,65114],{},[892,65110,65111],{},[996,65112,65113],{},"Report generation",[892,65115,65116],{},[996,65117,65118],{},"5-15 seconds",[877,65120,65121,65124],{},[892,65122,65123],{},"Full-text search across catalog",[892,65125,65126],{},"Sub-second",[877,65128,65129,65132],{},[892,65130,65131],{},"Dashboard aggregations",[892,65133,65134],{},"Real-time",[842,65136,65137],{},"The ability to generate complex royalty reports across 200 million records in just a few seconds is what sets the system apart. This is achieved through:",[958,65139,65140,65145,65151,65157],{},[961,65141,65142,65144],{},[996,65143,3033],{}," for aggregations and full-text search",[961,65146,65147,65150],{},[996,65148,65149],{},"Pre-computed rollups"," for common report dimensions",[961,65152,65153,65156],{},[996,65154,65155],{},"Chunked async processing"," during import (2000 rows per batch)",[961,65158,65159,65162],{},[996,65160,65161],{},"Celery task queues"," for parallel processing",[1045,65164,65173,65177,65180],{"className":65165},[65166,65057,65167,65168,35205,8190,65169,65170,65171,65172],"my-12","py-10","px-6","border-primary-200","dark:border-primary-800","bg-primary-50","dark:bg-primary-900/20",[1074,65174,65176],{"id":65175},"struggling-with-messy-royalty-data-from-multiple-distributors","Struggling with messy royalty data from multiple distributors?",[842,65178,65179],{},"We built this system and we can tailor it for your label. See how it works with your actual data.",[1045,65181,65183],{"className":65182},[13033,13034,8202],[50241,65184],{"color":50243,"icon":65185,"label":65186,"size":61022,"to":3802,"variant":65187},"i-lucide-calendar","Book a Demo","solid",[863,65189,65191],{"id":65190},"the-20-standard-fields","The 20 Standard Fields",[842,65193,65194],{},"After analyzing dozens of distributor formats, we defined a universal schema that captures all essential royalty data:",[871,65196,65197,65206],{},[874,65198,65199],{},[877,65200,65201,65203],{},[880,65202,48471],{},[880,65204,65205],{},"Fields",[887,65207,65208,65218,65228,65238,65248],{},[877,65209,65210,65215],{},[892,65211,65212],{},[996,65213,65214],{},"Identification",[892,65216,65217],{},"Artist, Track, Product (album), Label, UPC, EAN, ISRC",[877,65219,65220,65225],{},[892,65221,65222],{},[996,65223,65224],{},"Location",[892,65226,65227],{},"Country code (ISO), Territory name",[877,65229,65230,65235],{},[892,65231,65232],{},[996,65233,65234],{},"Time Period",[892,65236,65237],{},"Year, Month, Period begin/end dates",[877,65239,65240,65245],{},[892,65241,65242],{},[996,65243,65244],{},"Financial",[892,65246,65247],{},"Currency, Units (streams/downloads), Unit price, Income, Income type (GROSS/NET)",[877,65249,65250,65254],{},[892,65251,65252],{},[996,65253,5024],{},[892,65255,65256],{},"Retailer (Spotify, Apple Music, etc.), Service type (streaming, download, sync)",[1045,65258,65260],{"className":65259},[13033,13034,1052],[842,65261,65262],{},[1027,65263],{"alt":65264,"src":65265,"width":30896},"Advanced filters - querying across all 20 standard fields with full-text search","/images/blog/musicdatalab/filters.webp",[863,65267,65269],{"id":65268},"real-world-challenges-we-solved","Real-World Challenges We Solved",[1074,65271,65273],{"id":65272},"the-ada-complexity","The ADA Complexity",[842,65275,65276,65277,65280],{},"ADA (Warner's distribution arm) required ",[996,65278,65279],{},"5 different adapters"," due to format inconsistencies:",[871,65282,65283,65294],{},[874,65284,65285],{},[877,65286,65287,65289,65292],{},[880,65288,8035],{},[880,65290,65291],{},"Adapter",[880,65293,26215],{},[887,65295,65296,65305,65315,65325,65337],{},[877,65297,65298,65300,65302],{},[892,65299,4434],{},[892,65301,9870],{},[892,65303,65304],{},"Current production format",[877,65306,65307,65309,65312],{},[892,65308,1483],{},[892,65310,65311],{},"V2",[892,65313,65314],{},"New format introduced in 2024",[877,65316,65317,65319,65322],{},[892,65318,7948],{},[892,65320,65321],{},"V2Fix",[892,65323,65324],{},"Edge case corrections for V2",[877,65326,65327,65329,65332],{},[892,65328,7989],{},[892,65330,65331],{},"XLS",[892,65333,65334,65335,48346],{},"Legacy ",[895,65336,14203],{},[877,65338,65339,65341,65344],{},[892,65340,8131],{},[892,65342,65343],{},"Custom",[892,65345,65346,65348],{},[895,65347,14203],{}," files that actually contain CSV data",[1074,65350,65352],{"id":65351},"entity-resolution","Entity Resolution",[842,65354,65355],{},"We maintain mapping databases to normalize inconsistent naming:",[1013,65357,65360],{"className":65358,"code":65359,"language":1018,"meta":728},[1016],"\"SME\"                  →  \"Sony Music Entertainment\"\n\"SPOTIFY AB\"           →  \"Spotify\"\n\"UNITED KINGDOM (GB)\"  →  \"GB\"\n",[895,65361,65359],{"__ignoreMap":728},[1572,65363,65364],{},[842,65365,65366],{},"Entity resolution turned out to be one of the most valuable features - clean, unified naming across all platforms made reporting dramatically more useful.",[1045,65368,65370],{"className":65369},[13033,13034,1052],[842,65371,65372],{},[1027,65373],{"alt":65374,"src":65375,"width":30896},"Entity mapping - normalizing retailer names across distributors (TuneCore, Soundcloud, Slacker)","/images/blog/musicdatalab/mappings.webp",[1074,65377,65379],{"id":65378},"encoding-and-format-detection","Encoding and Format Detection",[842,65381,65382],{},"Every file goes through automatic format detection and encoding fallback - because \"just UTF-8\" is never the reality in international music data.",[863,65384,65386],{"id":65385},"technologies-used","Technologies Used",[1045,65388,65390,65393,65396,65400,65404,65408],{"className":65389},[1048,50574,50605,1051,1052,65057],[1054,65391],{"description":65392,"title":34475},"Backend",[1054,65394],{"description":65395,"title":3033},"Search & aggregations",[1054,65397],{"description":65398,"title":65399},"Async processing","Celery + Redis",[1054,65401],{"description":65402,"title":65403},"Relational storage","PostgreSQL",[1054,65405],{"description":65406,"title":65407},"Containerization","Docker",[1054,65409],{"description":65410,"title":65411},"Infrastructure","Digital Ocean",[863,65413,65415],{"id":65414},"results","Results",[842,65417,65418],{},"After 18 months of development:",[958,65420,65421,65427,65432,65438,65444],{},[961,65422,65423,65426],{},[996,65424,65425],{},"200 million records"," processed and searchable",[961,65428,65429,65431],{},[996,65430,64677],{}," for major music distributors",[961,65433,65434,65437],{},[996,65435,65436],{},"Report generation in 5-15 seconds"," regardless of data volume",[961,65439,65440,65443],{},[996,65441,65442],{},"Sub-second search"," across the entire catalog",[961,65445,65446,65449],{},[996,65447,65448],{},"Zero manual data entry"," - fully automated pipeline",[1045,65451,65453],{"className":65452},[13033,13034,1052],[842,65454,65455],{},[1027,65456],{"alt":65457,"src":65458,"width":30896},"Indexed data - browsing 200M+ streaming records with currency conversion and period tracking","/images/blog/musicdatalab/index-list.webp",[1045,65460,65462],{"className":65461},[13033,13034,1052],[842,65463,65464],{},[1027,65465],{"alt":65466,"src":65467,"width":30896},"Reports - royalty breakdown by artist and retailer with export to XLSX and CSV","/images/blog/musicdatalab/reports.webp",[1045,65469,65471],{"className":65470},[13033,13034,1052],[842,65472,65473],{},[1027,65474],{"alt":65475,"src":65476,"width":30896},"Reports - royalty breakdown by artist and product with final income","/images/blog/musicdatalab/reports-products.webp",[1074,65478,65480],{"id":65479},"development-in-numbers","Development in Numbers",[842,65482,65483],{},"Building a system of this complexity doesn't happen overnight. Here's what the development effort looked like behind the scenes:",[1045,65485,65487,65491,65495,65499],{"className":65486},[1048,50574,50605,1051,1052,65057],[1054,65488],{"description":65489,"title":65490},"Commits","727",[1054,65492],{"description":65493,"title":65494},"Files created","1,500+",[1054,65496],{"description":65497,"title":65498},"Active development days","84",[1054,65500],{"description":65501,"title":65502},"Peak commits in a single week","103",[842,65504,65505,65506,65509],{},"The development followed an intense sprint delivery model - 80% of all commits landed within a focused 3-month window (December 2024 – February 2025), averaging nearly ",[996,65507,65508],{},"9 commits per active day",". Each distributor adapter went through multiple iterations: for every new feature shipped, approximately 3 follow-up fixes were committed to handle real-world edge cases in date parsing, encoding, and field normalization.",[842,65511,65512],{},"This iterative approach - build, import real data, discover edge cases, fix, repeat - was essential. Music distribution data is messy by nature, and no amount of upfront design can replace the feedback loop of processing actual royalty files from 20+ platforms.",[1074,65514,15096],{"id":15095},[1032,65516,65517],{},[958,65518,65519,65525,65531,65537],{},[961,65520,65521,65524],{},[996,65522,65523],{},"Start with abstraction, add workarounds later"," - The Adapter Pattern gave us a clean base, but real-world data required pragmatic hacks.",[961,65526,65527,65530],{},[996,65528,65529],{},"Date parsing is always harder than you think"," - We encountered formats we never imagined existed.",[961,65532,65533,65536],{},[996,65534,65535],{},"Entity resolution is a feature, not a bug"," - Union mapping became one of the most valuable features for reporting.",[961,65538,65539,65542],{},[996,65540,65541],{},"Scale matters from day one"," - Designing for millions of records upfront saved us from painful rewrites.",[863,65544,31693],{"id":16446},[842,65546,65547],{},"We're exploring:",[958,65549,65550,65560,65566,65572],{},[961,65551,65552,65559],{},[996,65553,65554,65558],{},[846,65555,1983],{"href":65556,"rel":65557},"https://clickhouse.com/",[850]," as the analytics engine"," - For the next project of this scale, we would replace Elasticsearch with ClickHouse for analytical queries. Its columnar storage and vectorized execution are purpose-built for aggregating billions of rows, offering significantly faster performance for royalty reporting workloads",[961,65561,65562,65565],{},[996,65563,65564],{},"ML-based column mapping"," - Automatically detect field mappings for new distributors",[961,65567,65568,65571],{},[996,65569,65570],{},"Real-time streaming ingestion"," - Move from batch files to API integrations",[961,65573,65574,65577],{},[996,65575,65576],{},"Anomaly detection"," - Flag unusual patterns in royalty data",[1680,65579,65580],{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .s5tWE, html code.shiki .s5tWE{--shiki-light:#E53935;--shiki-light-font-style:italic;--shiki-default:#F07178;--shiki-default-font-style:italic;--shiki-dark:#F07178;--shiki-dark-font-style:italic}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 .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 .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 .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);}",{"title":728,"searchDepth":729,"depth":729,"links":65582},[65583,65588,65591,65596,65599,65603,65604,65609,65610,65614],{"id":64326,"depth":729,"text":64327,"children":65584},[65585,65586,65587],{"id":64364,"depth":1112,"text":64365},{"id":64460,"depth":1112,"text":64461},{"id":64467,"depth":1112,"text":64468},{"id":64477,"depth":729,"text":64478,"children":65589},[65590],{"id":26145,"depth":1112,"text":26146},{"id":64670,"depth":729,"text":64671,"children":65592},[65593,65594,65595],{"id":64681,"depth":1112,"text":64682},{"id":64789,"depth":1112,"text":64790},{"id":64881,"depth":1112,"text":64882},{"id":64911,"depth":729,"text":64912,"children":65597},[65598],{"id":65019,"depth":1112,"text":65020},{"id":65049,"depth":729,"text":65050,"children":65600},[65601,65602],{"id":65085,"depth":1112,"text":65086},{"id":65175,"depth":1112,"text":65176},{"id":65190,"depth":729,"text":65191},{"id":65268,"depth":729,"text":65269,"children":65605},[65606,65607,65608],{"id":65272,"depth":1112,"text":65273},{"id":65351,"depth":1112,"text":65352},{"id":65378,"depth":1112,"text":65379},{"id":65385,"depth":729,"text":65386},{"id":65414,"depth":729,"text":65415,"children":65611},[65612,65613],{"id":65479,"depth":1112,"text":65480},{"id":15095,"depth":1112,"text":15096},{"id":16446,"depth":729,"text":31693},{"name":65616,"logo":65617,"colorLogo":738},"CherryRed Records","/images/logos/cherryredrecords.webp","How we aggregated 200M streaming records from Spotify, Apple Music, YouTube and more into a unified royalty data integration system.",{"src":65620,"hasLogo":69},"/images/case-studies/musicdata-lab-universal-music-data-parser-case-study.webp",{"enabled":738,"items":65622},[65623,65625,65627,65629],{"text":65624,"icon":2895},"200M+ streaming records parsed from 25+ adapters covering Spotify, Apple Music, YouTube, and more.",{"text":65626,"icon":2939},"Royalty reports generated in 5-15 seconds across the entire 200M record dataset.",{"text":65628,"icon":8737},"Adding a new distributor adapter takes only 2-3 hours thanks to the Adapter Pattern.",{"text":65630,"icon":1779},"727 commits over 84 active development days with 80% shipped in a 3-month sprint.",{},{"title":38,"description":65618},[4990,731],"diVoP5kprNqXIFsAqtQbRWhZovPzdtIq4MGh8CoznAA",{"id":65636,"title":10,"authors":723,"badge":65637,"body":65638,"category":4990,"client":66100,"date":66103,"description":66104,"extension":734,"faq":723,"featured":738,"featuredOrder":3462,"hidden":69,"image":66105,"keyTakeaways":66107,"meta":66117,"navigation":738,"path":11,"seo":66118,"status":723,"stem":12,"tags":66119,"teaser":723,"__hash__":66120},"posts/blog/case-study/ambistream-building-a-multi-layer-streaming-platform-from-a-spark-of-an-idea.md",{"label":5,"color":50099},{"type":725,"value":65639,"toc":66087},[65640,65643,65650,65659,65668,65671,65673,65675,65715,65717,65721,65768,65770,65772,65817,65819,65823,65826,65945,65947,65951,65955,65961,65972,65975,65980,65982,65986,65989,65992,65997,65999,66003,66029,66031,66035,66042,66048,66050,66054,66057,66082,66085],[842,65641,65642],{},"Before Ambistream had a roadmap, a name, or even an interface, it began with a simple question at MusicTech Lab:",[41054,65644,65645],{},[842,65646,65647],{},[964,65648,65649],{},"“What if video could be controlled like a musical instrument?”",[842,65651,65652,65653,65655,65656,65658],{},"A Flutter app prototype.",[42386,65654],{},"\nA Chromecast on a table.",[42386,65657],{},"\nA folder full of rehearsal and training videos.",[842,65660,65661,65662,861,65665,65667],{},"One button: ",[996,65663,65664],{},"PLAY ON TV",[42386,65666],{},"\nAnd it worked - barely, but enough to start something bigger.",[842,65669,65670],{},"This is the story of how Ambistream grew from a curiosity into a fully-fledged streaming engine powering creative, educational, and athletic scenarios.",[4937,65672],{},[863,65674,52003],{"id":52002},[1045,65676,65678,65681,65685,65689,65692,65696,65700,65704,65708,65711],{"className":65677},[1048,1049,1765,52080,1051,1052],[1054,65679],{"description":65680,"icon":6395,"title":52010},"Django-based engine for sessions, media, metadata",[1054,65682],{"description":65683,"icon":7546,"title":65684},"Nuxt 3 platform for scene management and control","Web Frontend",[1054,65686],{"description":65687,"icon":4855,"title":65688},"Flutter app for iOS/Android with casting capabilities","Mobile Application",[1054,65690],{"description":65691,"icon":64101,"title":64102},"Dedicated cast app for synchronised playback",[1054,65693],{"description":65694,"icon":64106,"title":65695},"Native remote playback for Apple devices","AirPlay Integration",[1054,65697],{"description":65698,"icon":1769,"title":65699},"Layered video + Lottie animations + UI","Overlay Engine",[1054,65701],{"description":65702,"icon":64247,"title":65703},"FFmpeg-based transcoding for HLS and MP4","Media Pipeline",[1054,65705],{"description":65706,"icon":2917,"title":65707},"Google Cloud Run, Firebase Hosting","Cloud Infrastructure",[1054,65709],{"description":65710,"icon":13562,"title":52105},"Tools for uploading, organising and configuring scenes",[1054,65712],{"description":65713,"icon":3649,"title":65714},"Sentry, UptimeRobot, Cloud Logging","Analytics & Monitoring",[4937,65716],{},[863,65718,65720],{"id":65719},"core-features","Core Features",[1045,65722,65724,65728,65733,65738,65742,65746,65750,65755,65760,65764],{"className":65723},[1048,1049,1765,1051,1052],[1054,65725],{"description":65726,"icon":1769,"title":65727},"Combine video with Lottie animations in real time","Layered Playback",[1054,65729],{"description":65730,"icon":65731,"title":65732},"Timeline-based overlay configuration","i-lucide-clapperboard","Scene Builder",[1054,65734],{"description":65735,"icon":65736,"title":65737},"Control playback from phone to external display","i-lucide-tablet-smartphone","Remote Controller",[1054,65739],{"description":65740,"icon":64101,"title":65741},"Seamless streaming to TVs and monitors","Chromecast / AirPlay Casting",[1054,65743],{"description":65744,"icon":64120,"title":65745},"Loops, slow-motion, segment markers","Playback Controls",[1054,65747],{"description":65748,"icon":7582,"title":65749},"Upload and manage assets","Media Library",[1054,65751],{"description":65752,"icon":65753,"title":65754},"Synchronised playback across devices","i-lucide-monitor-smartphone","Device Sync",[1054,65756],{"description":65757,"icon":65758,"title":65759},"Local caching on mobile","i-lucide-wifi-off","Offline Mode (PoC)",[1054,65761],{"description":65762,"icon":4845,"title":65763},"Role-based access and user management","User Accounts & Permissions",[1054,65765],{"description":65766,"icon":2939,"title":65767},"Experimental real-time streaming using WebRTC","Low-Latency Mode",[4937,65769],{},[863,65771,65386],{"id":65385},[1045,65773,65775,65778,65782,65785,65789,65793,65796,65799,65802,65805,65809,65813],{"className":65774},[1048,50574,1050,50642,50239,1052],[1054,65776],{"description":65777,"icon":4855,"title":47091},"Mobile & controller",[1054,65779],{"description":65780,"icon":1067,"title":65781},"Web platform","Nuxt 3",[1054,65783],{"description":52010,"icon":6395,"title":65784},"Django",[1054,65786],{"description":65787,"icon":2917,"title":65788},"Scalable backend","Cloud Run",[1054,65790],{"description":65791,"icon":65792,"title":26084},"Frontend hosting","i-lucide-flame",[1054,65794],{"description":65795,"icon":64247,"title":64248},"Transcoding",[1054,65797],{"description":65798,"icon":12409,"title":64252},"Animations",[1054,65800],{"description":65801,"icon":64101,"title":64234},"Cast receiver",[1054,65803],{"description":65804,"icon":64106,"title":64238},"Apple devices",[1054,65806],{"description":65807,"icon":65808,"title":65407},"Containers","i-lucide-container",[1054,65810],{"description":65811,"icon":1779,"title":65812},"CI/CD","GitHub Actions",[1054,65814],{"description":65815,"icon":64001,"title":65816},"Error tracking","Sentry",[4937,65818],{},[863,65820,65822],{"id":65821},"architecture-layered-rendering-engine","Architecture: Layered Rendering Engine",[842,65824,65825],{},"The core innovation behind Ambistream is its layered rendering approach — video, animations, and UI controls are separate layers composed in real time:",[1013,65827,65829],{"className":3341,"code":65828,"language":3343,"meta":728,"style":728},"graph TB\n    subgraph Controller[\"📱 Mobile Controller\"]\n        A[Flutter App] --> B[Playback Commands]\n    end\n\n    subgraph Backend[\"☁️ Cloud Backend\"]\n        C[Django API] --> D[Media Pipeline]\n        D --> E[HLS / MP4 Transcoding]\n    end\n\n    subgraph Display[\"📺 Display Device\"]\n        F[Video Layer]\n        G[Lottie Animation Layer]\n        H[UI Overlay Layer]\n        F --> I[Composited Output]\n        G --> I\n        H --> I\n    end\n\n    B -->|WebSocket / REST| C\n    B -->|Cast Protocol| Display\n    E -->|Stream| F\n    C -->|Scene Config| G\n    C -->|Controls| H\n",[895,65830,65831,65836,65841,65846,65850,65854,65859,65864,65869,65873,65877,65882,65887,65892,65897,65902,65907,65912,65916,65920,65925,65930,65935,65940],{"__ignoreMap":728},[1086,65832,65833],{"class":1088,"line":1089},[1086,65834,65835],{"class":1436},"graph TB\n",[1086,65837,65838],{"class":1088,"line":729},[1086,65839,65840],{"class":1436},"    subgraph Controller[\"📱 Mobile Controller\"]\n",[1086,65842,65843],{"class":1088,"line":1112},[1086,65844,65845],{"class":1436},"        A[Flutter App] --> B[Playback Commands]\n",[1086,65847,65848],{"class":1088,"line":1181},[1086,65849,13084],{"class":1436},[1086,65851,65852],{"class":1088,"line":1205},[1086,65853,3390],{"emptyLinePlaceholder":738},[1086,65855,65856],{"class":1088,"line":1276},[1086,65857,65858],{"class":1436},"    subgraph Backend[\"☁️ Cloud Backend\"]\n",[1086,65860,65861],{"class":1088,"line":1282},[1086,65862,65863],{"class":1436},"        C[Django API] --> D[Media Pipeline]\n",[1086,65865,65866],{"class":1088,"line":1288},[1086,65867,65868],{"class":1436},"        D --> E[HLS / MP4 Transcoding]\n",[1086,65870,65871],{"class":1088,"line":2685},[1086,65872,13084],{"class":1436},[1086,65874,65875],{"class":1088,"line":2700},[1086,65876,3390],{"emptyLinePlaceholder":738},[1086,65878,65879],{"class":1088,"line":3398},[1086,65880,65881],{"class":1436},"    subgraph Display[\"📺 Display Device\"]\n",[1086,65883,65884],{"class":1088,"line":1715},[1086,65885,65886],{"class":1436},"        F[Video Layer]\n",[1086,65888,65889],{"class":1088,"line":3409},[1086,65890,65891],{"class":1436},"        G[Lottie Animation Layer]\n",[1086,65893,65894],{"class":1088,"line":3415},[1086,65895,65896],{"class":1436},"        H[UI Overlay Layer]\n",[1086,65898,65899],{"class":1088,"line":3421},[1086,65900,65901],{"class":1436},"        F --> I[Composited Output]\n",[1086,65903,65904],{"class":1088,"line":3427},[1086,65905,65906],{"class":1436},"        G --> I\n",[1086,65908,65909],{"class":1088,"line":3433},[1086,65910,65911],{"class":1436},"        H --> I\n",[1086,65913,65914],{"class":1088,"line":3439},[1086,65915,13084],{"class":1436},[1086,65917,65918],{"class":1088,"line":3444},[1086,65919,3390],{"emptyLinePlaceholder":738},[1086,65921,65922],{"class":1088,"line":3450},[1086,65923,65924],{"class":1436},"    B -->|WebSocket / REST| C\n",[1086,65926,65927],{"class":1088,"line":3456},[1086,65928,65929],{"class":1436},"    B -->|Cast Protocol| Display\n",[1086,65931,65932],{"class":1088,"line":3462},[1086,65933,65934],{"class":1436},"    E -->|Stream| F\n",[1086,65936,65937],{"class":1088,"line":3467},[1086,65938,65939],{"class":1436},"    C -->|Scene Config| G\n",[1086,65941,65942],{"class":1088,"line":3473},[1086,65943,65944],{"class":1436},"    C -->|Controls| H\n",[4937,65946],{},[863,65948,65950],{"id":65949},"the-story-from-spark-to-platform","The Story: From Spark to Platform",[1074,65952,65954],{"id":65953},"it-started-with-a-question","It started with a question",[842,65956,65957,65958],{},"In 2023, while working on several MusicTech Lab products, one theme kept coming up:\n",[996,65959,65960],{},"creators and coaches needed better, smarter playback tools.",[958,65962,65963,65966,65969],{},[961,65964,65965],{},"Musicians needed visual cues over their training videos.",[961,65967,65968],{},"Swimming and sports coaches needed slow-motion loops.",[961,65970,65971],{},"Stage performers wanted remote-controlled scenes and overlays.",[842,65973,65974],{},"But existing video players couldn’t do any of this. So we built a tiny PoC — Flutter, Chromecast, one big button, one video. It played. It glitched. But it proved the idea possible.",[842,65976,65977],{},[996,65978,65979],{},"The spark was lit.",[4937,65981],{},[1074,65983,65985],{"id":65984},"the-journey","The Journey",[3572,65987],{":items":65988},"[{\"title\":\"R&D — The Deep Dive\",\"description\":\"Explored HLS, DASH, MP4, WebRTC. Tested on old Android TVs and new 4K displays. Discovered Lottie as the key to lightweight, animatable overlays. The layered rendering engine was born: Video → Lottie → UI.\",\"icon\":\"i-lucide-microscope\"},{\"title\":\"MVP — Building the First Version\",\"description\":\"Built a Flutter controller, Nuxt dashboard, Django backend, and custom Chromecast receiver. For the first time: phone controlled TV precisely, videos synced across devices, and overlays played in time with footage.\",\"icon\":\"i-lucide-hammer\"},{\"title\":\"Real Use-Cases Emerge\",\"description\":\"Adopted by coaches, instructors, and creators — music training with tempo cues, swimming coaching with slow-motion replays, live events with remote-controlled scenes, and step-by-step educational overlays.\",\"icon\":\"i-lucide-users\"},{\"title\":\"Scaling Up\",\"description\":\"Multi-device sync, reliable casting for Android/iOS, Cloud Run deployment, automated transcoding pipelines, Scene Builder with timelines, analytics, monitoring, and offline caching prototype.\",\"icon\":\"i-lucide-rocket\"},{\"title\":\"Ambistream 2.0 — The Vision\",\"description\":\"Preparing for broader release as a white-label solution, multi-tenant platform, production-grade streaming engine, and customisable scene editor — a toolkit for creators, athletes, teachers, and performers.\",\"icon\":\"i-lucide-sparkles\"}]",[842,65990,65991],{},"The system started from a spark — now it’s a cornerstone product in the MusicTech Lab ecosystem. And the story continues, frame by frame.",[1032,65993,65994],{},[842,65995,65996],{},"Ambistream is a MusicTech Lab internal product — built from a spark of an idea into a production-ready streaming engine. It demonstrates how iterative development can turn a simple PoC into a multi-platform system.",[4937,65998],{},[863,66000,66002],{"id":66001},"services-deliverables","Services & Deliverables",[1045,66004,66006,66010,66015,66020,66022,66025],{"className":66005},[1048,1049,1050,1051,1052],[1054,66007],{"description":66008,"icon":5365,"title":66009},"Backend, frontend, mobile app, and API","Full-Stack Development",[1054,66011],{"description":66012,"icon":66013,"title":66014},"From PoC to production-grade platform","i-lucide-flask-conical","R&D & Prototyping",[1054,66016],{"description":66017,"icon":66018,"title":66019},"Vision, roadmap, and feature prioritisation","i-lucide-compass","Product Strategy",[1054,66021],{"description":65706,"icon":2917,"title":65707},[1054,66023],{"description":65702,"icon":64247,"title":66024},"Video Pipeline",[1054,66026],{"description":66027,"icon":5507,"title":66028},"Controller and dashboard flows","UI/UX",[4937,66030],{},[863,66032,66034],{"id":66033},"client-testimonial","Client Testimonial",[41054,66036,66037],{},[842,66038,66039],{},[964,66040,66041],{},"“Ambistream allowed us to build a synchronised, multi-layer playback system we simply couldn’t find on the market. The combination of mobile control, casting, and overlays created entirely new possibilities for training and creative work.”",[842,66043,66044,66047],{},[996,66045,66046],{},"Ambistream Team","\nNew York, USA",[4937,66049],{},[863,66051,66053],{"id":66052},"summary-streaming-innovation-creative-control","Summary: Streaming Innovation & Creative Control",[842,66055,66056],{},"Ambistream shows that even highly complex media systems can be developed iteratively:",[1045,66058,66060,66064,66069,66074,66077],{"className":66059},[1048,50574,50575,50239,1052],[1054,66061],{"description":66062,"icon":50270,"title":66063},"Spark of an idea","PoC",[1054,66065],{"description":66066,"icon":66067,"title":66068},"Deep technical dive","i-lucide-microscope","R&D",[1054,66070],{"description":66071,"icon":66072,"title":66073},"First working version","i-lucide-hammer","MVP",[1054,66075],{"description":66076,"icon":37696,"title":12317},"Real customers",[1054,66078],{"description":66079,"icon":66080,"title":66081},"Broader release","i-lucide-building","White-label",[842,66083,66084],{},"The platform is now used for training, creative performance, live educational formats, and internal production workflows.",[1680,66086,3806],{},{"title":728,"searchDepth":729,"depth":729,"links":66088},[66089,66090,66091,66092,66093,66097,66098,66099],{"id":52002,"depth":729,"text":52003},{"id":65719,"depth":729,"text":65720},{"id":65385,"depth":729,"text":65386},{"id":65821,"depth":729,"text":65822},{"id":65949,"depth":729,"text":65950,"children":66094},[66095,66096],{"id":65953,"depth":1112,"text":65954},{"id":65984,"depth":1112,"text":65985},{"id":66001,"depth":729,"text":66002},{"id":66033,"depth":729,"text":66034},{"id":66052,"depth":729,"text":66053},{"name":66101,"logo":66102,"colorLogo":738},"Ambistream","/images/logos/ambistream.webp","2025-01-01T00:00:00.000Z","How a late-night experiment grew into a multi-platform streaming engine with overlays, remote control, and Chromecast/AirPlay support.",{"src":66106},"/images/blog/musictechlab_blog_ambistream-streaming-platform.webp",{"enabled":738,"items":66108},[66109,66111,66113,66115],{"text":66110,"icon":1769},"Multi-layer streaming engine combines video, Lottie animations, and UI overlays in real time.",{"text":66112,"icon":37696},"Started as a single-button Flutter PoC and grew into a production platform.",{"text":66114,"icon":64101},"Supports Chromecast and AirPlay casting with phone-as-remote-controller design.",{"text":66116,"icon":4845},"Used by coaches, musicians, and performers for training and live event scenarios.",{},{"title":10,"description":66104},[4990,5523],"B64OkdTDsJdr_qBvySu0HCAHEVSrkHKCtW4dmc28UYI",{"id":66122,"title":221,"authors":66123,"badge":723,"body":66126,"category":5678,"client":723,"date":66960,"description":66961,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":66962,"keyTakeaways":723,"meta":66964,"navigation":738,"path":222,"seo":66965,"status":723,"stem":223,"tags":66966,"teaser":723,"__hash__":66967},"posts/blog/newsletter/music-industry-tech-openings-december-2024-update.md",[66124],{"name":50093,"to":50094,"avatar":66125},{"src":50096},{"type":725,"value":66127,"toc":66930},[66128,66130,66139,66143,66148,66151,66161,66166,66190,66192,66200,66204,66216,66220,66230,66234,66239,66243,66252,66257,66274,66278,66288,66292,66317,66321,66331,66335,66346,66348,66356,66361,66372,66376,66382,66386,66394,66396,66405,66409,66416,66418,66428,66432,66440,66442,66450,66454,66459,66463,66473,66478,66491,66495,66501,66505,66510,66512,66522,66527,66545,66549,66559,66564,66582,66584,66592,66597,66606,66608,66618,66623,66651,66653,66661,66665,66674,66677,66686,66690,66709,66711,66719,66724,66735,66737,66745,66749,66768,66771,66779,66783,66791,66793,66801,66805,66827,66831,66841,66846,66857,66859,66868,66873,66880,66884,66894,66899,66907,66910,66918,66922],[1074,66129,52521],{"id":52518},[842,66131,66132,52297,66135],{},[846,66133,5669],{"href":52526,"rel":66134},[850],[846,66136,52302],{"href":66137,"rel":66138},"https://join.com/companies/ai-coustics/13172920-audio-data-engineer",[850],[842,66140,66141,52434],{},[996,66142,52307],{},[958,66144,66145],{},[961,66146,66147],{},"Audio Data Engineer",[1074,66149,5059],{"id":66150},"amazon-music",[842,66152,66153,52297,66157],{},[846,66154,5669],{"href":66155,"rel":66156},"https://www.linkedin.com/company/amazon-music/",[850],[846,66158,52302],{"href":66159,"rel":66160},"https://www.amazon.jobs/content/en/job-categories",[850],[842,66162,66163,66165],{},[996,66164,52307],{}," 🇮🇳 🇺🇸 🇨🇴 🇲🇽",[958,66167,66168,66171,66173,66176,66179,66182,66185,66188],{},[961,66169,66170],{},"Quality Assurance Engineer",[961,66172,56600],{},[961,66174,66175],{},"System Development Engineer",[961,66177,66178],{},"SDE-II",[961,66180,66181],{},"Quality Assurance Technician",[961,66183,66184],{},"Audiobooks Programmer",[961,66186,66187],{},"Music Programmer",[961,66189,53093],{},[1074,66191,53630],{"id":53627},[842,66193,66194,52297,66197],{},[846,66195,5669],{"href":53635,"rel":66196},[850],[846,66198,52302],{"href":53639,"rel":66199},[850],[842,66201,66202,53032],{},[996,66203,52307],{},[958,66205,66206,66209,66212,66214],{},[961,66207,66208],{},"Medior Backend Developer",[961,66210,66211],{},"Back-End/DevOps Engineer",[961,66213,57241],{},[961,66215,53501],{},[1074,66217,66219],{"id":66218},"disney-music-group","Disney Music Group",[842,66221,66222,52297,66226],{},[846,66223,5669],{"href":66224,"rel":66225},"https://www.linkedin.com/company/disneymusicgroup/",[850],[846,66227,52302],{"href":66228,"rel":66229},"https://www.disneycareers.com/en/business/custom_fields.industrycustomfield/disney%20music%20group/391/5",[850],[842,66231,66232,52615],{},[996,66233,52307],{},[958,66235,66236],{},[961,66237,66238],{},"Data Science Manager",[1074,66240,66242],{"id":66241},"dolby-laboratories","Dolby Laboratories",[842,66244,66245,52297,66248],{},[846,66246,5669],{"href":57256,"rel":66247},[850],[846,66249,52302],{"href":66250,"rel":66251},"https://jobs.dolby.com/careers?pid=26620021&domain=dolby.com&sort_by=relevance",[850],[842,66253,66254,66256],{},[996,66255,52307],{}," 🇬🇧 🇺🇸 🇮🇪 🇵🇱",[958,66258,66259,66262,66265,66268,66271],{},[961,66260,66261],{},"Audio & Video Support Engineer Intern",[961,66263,66264],{},"Broadcast Student Placement",[961,66266,66267],{},"Digital Platforms Intern",[961,66269,66270],{},"Audio Software Engineer Intern",[961,66272,66273],{},"IT Support Intern",[1074,66275,66277],{"id":66276},"elsewhere-entertainment","Elsewhere Entertainment",[842,66279,66280,52297,66284],{},[846,66281,5669],{"href":66282,"rel":66283},"https://www.linkedin.com/company/elsewhere-entertainment/",[850],[846,66285,52302],{"href":66286,"rel":66287},"https://careers.activision.com/Elsewhere",[850],[842,66289,66290,53359],{},[996,66291,52307],{},[958,66293,66294,66296,66299,66302,66305,66308,66311,66314],{},[961,66295,53805],{},[961,66297,66298],{},"Narrative Systems Designer",[961,66300,66301],{},"Tools Designer",[961,66303,66304],{},"Physics Programmer",[961,66306,66307],{},"Senior Technical Designer",[961,66309,66310],{},"Principal Audio Programmer",[961,66312,66313],{},"Senior Tools Engineer",[961,66315,66316],{},"Technical QA Analyst",[1074,66318,66320],{"id":66319},"engineears","EngineEars",[842,66322,66323,52297,66327],{},[846,66324,5669],{"href":66325,"rel":66326},"https://www.linkedin.com/company/engineears/",[850],[846,66328,52302],{"href":66329,"rel":66330},"https://app.dover.com/EngineEars/careers/f0b0dced-bfc1-4d97-8e62-387773d1ef10",[850],[842,66332,66333,52615],{},[996,66334,52307],{},[958,66336,66337,66339,66341,66344],{},[961,66338,57724],{},[961,66340,54614],{},[961,66342,66343],{},"Video Editor",[961,66345,52782],{},[1074,66347,52356],{"id":52353},[842,66349,66350,52297,66353],{},[846,66351,5669],{"href":52361,"rel":66352},[850],[846,66354,52302],{"href":55959,"rel":66355},[850],[842,66357,66358,66360],{},[996,66359,52307],{}," 🇩🇪 🇯🇵",[958,66362,66363,66365,66367,66369],{},[961,66364,57692],{},[961,66366,52406],{},[961,66368,52409],{},[961,66370,66371],{},"Artist Marketing Manager",[1074,66373,66375],{"id":66374},"global-tour-creatives","Global Tour Creatives",[842,66377,66378],{},[846,66379,5669],{"href":66380,"rel":66381},"https://www.linkedin.com/company/global-tour-creatives/",[850],[842,66383,66384,52615],{},[996,66385,52307],{},[958,66387,66388,66391],{},[961,66389,66390],{},"Junior Graphic Designer",[961,66392,66393],{},"Senior Designer/Art Director",[1074,66395,52982],{"id":52979},[842,66397,66398,52297,66402],{},[846,66399,5669],{"href":66400,"rel":66401},"https://www.linkedin.com/company/ice-services/",[850],[846,66403,52302],{"href":52991,"rel":66404},[850],[842,66406,66407,52997],{},[996,66408,52307],{},[958,66410,66411,66413],{},[961,66412,53037],{},[961,66414,66415],{},"Senior Backend Engineer – Royalty Calculation",[1074,66417,54468],{"id":54465},[842,66419,66420,52297,66424],{},[846,66421,5669],{"href":66422,"rel":66423},"https://www.linkedin.com/company/luminate/",[850],[846,66425,52302],{"href":66426,"rel":66427},"https://luminatedata.com/careers/#open-positions",[850],[842,66429,66430,52615],{},[996,66431,52307],{},[958,66433,66434,66436,66438],{},[961,66435,54490],{},[961,66437,58997],{},[961,66439,52653],{},[1074,66441,57505],{"id":57502},[842,66443,66444,52297,66447],{},[846,66445,5669],{"href":57510,"rel":66446},[850],[846,66448,52302],{"href":57516,"rel":66449},[850],[842,66451,66452,52615],{},[996,66453,52307],{},[958,66455,66456],{},[961,66457,66458],{},"Senior Product Manager, Creator",[1074,66460,66462],{"id":66461},"roc-nation","Roc Nation",[842,66464,66465,52297,66469],{},[846,66466,5669],{"href":66467,"rel":66468},"https://www.linkedin.com/company/rocnation/",[850],[846,66470,52302],{"href":66471,"rel":66472},"https://livenation.wd1.myworkdayjobs.com/RNExternalSite",[850],[842,66474,66475,66477],{},[996,66476,52307],{}," 🇬🇧 🇺🇸",[958,66479,66480,66482,66485,66488],{},[961,66481,55084],{},[961,66483,66484],{},"Project Manager Creative",[961,66486,66487],{},"Manager, Web Development",[961,66489,66490],{},"Manager, Content Design",[1074,66492,66494],{"id":66493},"septien-entertainment-group","Septien Entertainment Group",[842,66496,66497],{},[846,66498,5669],{"href":66499,"rel":66500},"https://www.linkedin.com/company/septien-entertainment-group/",[850],[842,66502,66503,52615],{},[996,66504,52307],{},[958,66506,66507],{},[961,66508,66509],{},"AI Expert",[1074,66511,5070],{"id":12966},[842,66513,66514,52297,66518],{},[846,66515,5669],{"href":66516,"rel":66517},"https://www.linkedin.com/company/spotify/",[850],[846,66519,52302],{"href":66520,"rel":66521},"https://www.lifeatspotify.com/jobs",[850],[842,66523,66524,66526],{},[996,66525,52307],{}," 🇬🇧 🇺🇸 🇸🇪 🇰🇷",[958,66528,66529,66532,66534,66536,66539,66542],{},[961,66530,66531],{},"Junior Backend Engineer",[961,66533,52546],{},[961,66535,52439],{},[961,66537,66538],{},"Partner Engineer",[961,66540,66541],{},"K-Pop Marketing Producer",[961,66543,66544],{},"Staff Designer",[1074,66546,66548],{"id":66547},"telus-digital-ai-data-solution","TELUS Digital AI Data Solution",[842,66550,66551,52297,66555],{},[846,66552,5669],{"href":66553,"rel":66554},"https://www.linkedin.com/company/telus-digital/",[850],[846,66556,52302],{"href":66557,"rel":66558},"https://jobs.telusdigital.com/en_US/careers/JobsIT/?pipelineOffset=0",[850],[842,66560,66561,66563],{},[996,66562,52307],{}," 🇯🇵 🇺🇸 🇵🇭 🇮🇳",[958,66565,66566,66569,66571,66574,66577,66579],{},[961,66567,66568],{},"Online Data Analyst",[961,66570,53501],{},[961,66572,66573],{},"DevOps Analyst",[961,66575,66576],{},"Video Streaming Application Support Analyst",[961,66578,53093],{},[961,66580,66581],{},"Software Development Engineer in Test",[1074,66583,56369],{"id":56366},[842,66585,66586,52297,66589],{},[846,66587,5669],{"href":56374,"rel":66588},[850],[846,66590,52302],{"href":56378,"rel":66591},[850],[842,66593,66594,66596],{},[996,66595,52307],{}," 🇫🇷 🇪🇬",[958,66598,66599,66601,66603],{},[961,66600,52504],{},[961,66602,56409],{},[961,66604,66605],{},"Project Manager Assistant - Stage",[1074,66607,52701],{"id":52698},[842,66609,66610,52297,66614],{},[846,66611,5669],{"href":66612,"rel":66613},"https://www.linkedin.com/company/live-nation/",[850],[846,66615,52302],{"href":66616,"rel":66617},"https://livenation.wd1.myworkdayjobs.com/LNExternalSite?jobFamilyGroup=",[850],[842,66619,66620,66622],{},[996,66621,52307],{}," 🇬🇧 🇬🇷 🇨🇦 🇺🇸",[958,66624,66625,66628,66631,66633,66635,66637,66639,66641,66643,66646,66648],{},[961,66626,66627],{},"Lead Engineer - Image & Update Services",[961,66629,66630],{},"Manager, Software Engineering",[961,66632,55027],{},[961,66634,52504],{},[961,66636,57580],{},[961,66638,57583],{},[961,66640,52748],{},[961,66642,57566],{},[961,66644,66645],{},"Principal Data Engineer (Databricks Platform)",[961,66647,52736],{},[961,66649,66650],{},"Technical Analyst - Marketing Insights",[1074,66652,56962],{"id":56959},[842,66654,66655,52297,66658],{},[846,66656,5669],{"href":56967,"rel":66657},[850],[846,66659,52302],{"href":56973,"rel":66660},[850],[842,66662,66663,52615],{},[996,66664,52307],{},[958,66666,66667,66669,66672],{},[961,66668,56989],{},[961,66670,66671],{},"Senior Backend Engineer - ML Core",[961,66673,58997],{},[1074,66675,59250],{"id":66676},"epidemic-sound",[842,66678,66679,52297,66682],{},[846,66680,5669],{"href":59253,"rel":66681},[850],[846,66683,52302],{"href":66684,"rel":66685},"https://epidemic-sound.teamtailor.com/jobs",[850],[842,66687,66688,53060],{},[996,66689,52307],{},[958,66691,66692,66694,66697,66700,66702,66704,66706],{},[961,66693,53037],{},[961,66695,66696],{},"Senior Product Manager - Music Distribution & Partnerships",[961,66698,66699],{},"Senior Product Manager - Sound Production & Platform",[961,66701,58997],{},[961,66703,55027],{},[961,66705,59273],{},[961,66707,66708],{},"Senior Data Platform Engineer - Data Infrastructure",[1074,66710,54498],{"id":54495},[842,66712,66713,52297,66716],{},[846,66714,5669],{"href":54503,"rel":66715},[850],[846,66717,52302],{"href":54507,"rel":66718},[850],[842,66720,66721,66723],{},[996,66722,52307],{}," 🇸🇬",[958,66725,66726,66728,66730,66733],{},[961,66727,58835],{},[961,66729,58838],{},[961,66731,66732],{},"Backend Engineer, Social Team",[961,66734,58841],{},[1074,66736,58011],{"id":58008},[842,66738,66739,52297,66742],{},[846,66740,5669],{"href":58016,"rel":66741},[850],[846,66743,52302],{"href":58022,"rel":66744},[850],[842,66746,66747,52957],{},[996,66748,52307],{},[958,66750,66751,66753,66755,66757,66760,66762,66765],{},[961,66752,53931],{},[961,66754,58034],{},[961,66756,55659],{},[961,66758,66759],{},"Senior Frontend Software Engineer",[961,66761,55588],{},[961,66763,66764],{},"Staff Frontend Software Engineer",[961,66766,66767],{},"Staff iOS Engineer",[1074,66769,59138],{"id":66770},"voicemod",[842,66772,66773,52297,66776],{},[846,66774,5669],{"href":59141,"rel":66775},[850],[846,66777,52302],{"href":59145,"rel":66778},[850],[842,66780,66781,52685],{},[996,66782,52307],{},[958,66784,66785,66788],{},[961,66786,66787],{},"Senior Analytics Engineer",[961,66789,66790],{},"Senior Backend Developer",[1074,66792,26104],{"id":57602},[842,66794,66795,52297,66798],{},[846,66796,5669],{"href":57609,"rel":66797},[850],[846,66799,52302],{"href":57615,"rel":66800},[850],[842,66802,66803,52615],{},[996,66804,52307],{},[958,66806,66807,66809,66811,66814,66817,66819,66821,66823,66825],{},[961,66808,55180],{},[961,66810,57654],{},[961,66812,66813],{},"Machine Learning Scientist",[961,66815,66816],{},"Software Engineering Internship",[961,66818,54490],{},[961,66820,57643],{},[961,66822,52626],{},[961,66824,57648],{},[961,66826,57651],{},[1074,66828,66830],{"id":66829},"soundcloud","SoundCloud",[842,66832,66833,52297,66837],{},[846,66834,5669],{"href":66835,"rel":66836},"https://www.linkedin.com/company/soundcloud/",[850],[846,66838,52302],{"href":66839,"rel":66840},"https://careers.soundcloud.com/",[850],[842,66842,66843,66845],{},[996,66844,52307],{}," 🇩🇪 🇬🇧 🇺🇸",[958,66847,66848,66851,66854],{},[961,66849,66850],{},"Backend Senior Software Engineer - Music Discovery",[961,66852,66853],{},"Senior Media Streaming Backend Engineer",[961,66855,66856],{},"Senior Data Analyst - Ad Products",[1074,66858,56343],{"id":56340},[842,66860,66861,52297,66864],{},[846,66862,5669],{"href":56348,"rel":66863},[850],[846,66865,52302],{"href":66866,"rel":66867},"https://serato.com/careers",[850],[842,66869,66870,66872],{},[996,66871,52307],{}," 🇳🇿",[958,66874,66875,66877],{},[961,66876,56361],{},[961,66878,66879],{},"Principal Software Engineer C++",[1074,66881,66883],{"id":66882},"dea-research","DEA Research",[842,66885,66886,52297,66890],{},[846,66887,5669],{"href":66888,"rel":66889},"https://www.linkedin.com/school/howest-dae/",[850],[846,66891,52302],{"href":66892,"rel":66893},"https://daeresearch.be/job_openings/",[850],[842,66895,66896,66898],{},[996,66897,52307],{}," 🇧🇪",[958,66900,66901,66904],{},[961,66902,66903],{},"Technical Artist (Houdini)",[961,66905,66906],{},"Researcher/Developer",[1074,66908,59156],{"id":66909},"lyricfind",[842,66911,66912,52297,66915],{},[846,66913,5669],{"href":59159,"rel":66914},[850],[846,66916,52302],{"href":59163,"rel":66917},[850],[842,66919,66920,52615],{},[996,66921,52307],{},[958,66923,66924,66926,66928],{},[961,66925,59169],{},[961,66927,56406],{},[961,66929,52348],{},{"title":728,"searchDepth":729,"depth":729,"links":66931},[66932,66933,66934,66935,66936,66937,66938,66939,66940,66941,66942,66943,66944,66945,66946,66947,66948,66949,66950,66951,66952,66953,66954,66955,66956,66957,66958,66959],{"id":52518,"depth":1112,"text":52521},{"id":66150,"depth":1112,"text":5059},{"id":53627,"depth":1112,"text":53630},{"id":66218,"depth":1112,"text":66219},{"id":66241,"depth":1112,"text":66242},{"id":66276,"depth":1112,"text":66277},{"id":66319,"depth":1112,"text":66320},{"id":52353,"depth":1112,"text":52356},{"id":66374,"depth":1112,"text":66375},{"id":52979,"depth":1112,"text":52982},{"id":54465,"depth":1112,"text":54468},{"id":57502,"depth":1112,"text":57505},{"id":66461,"depth":1112,"text":66462},{"id":66493,"depth":1112,"text":66494},{"id":12966,"depth":1112,"text":5070},{"id":66547,"depth":1112,"text":66548},{"id":56366,"depth":1112,"text":56369},{"id":52698,"depth":1112,"text":52701},{"id":56959,"depth":1112,"text":56962},{"id":66676,"depth":1112,"text":59250},{"id":54495,"depth":1112,"text":54498},{"id":58008,"depth":1112,"text":58011},{"id":66770,"depth":1112,"text":59138},{"id":57602,"depth":1112,"text":26104},{"id":66829,"depth":1112,"text":66830},{"id":56340,"depth":1112,"text":56343},{"id":66882,"depth":1112,"text":66883},{"id":66909,"depth":1112,"text":59156},"2024-12-30T00:00:00.000Z","Curated list of tech job openings in the music industry for December 2024. Find roles at music startups, labels, and streaming platforms.",{"src":66963},"/images/blog/musictechlab_blog_music-industry-tech-openings-december-2024-update.webp",{},{"title":221,"description":66961},[5678,5523],"jLIXoBgKvkaIh-Osyh0-awnkpZBRCLn533TXkL-5E4E",{"id":66969,"title":530,"authors":66970,"badge":66973,"body":66974,"category":756,"client":723,"date":67610,"description":67611,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":67612,"keyTakeaways":67614,"meta":67624,"navigation":738,"path":531,"seo":67625,"status":723,"stem":532,"tags":67626,"teaser":723,"__hash__":67627},"posts/blog/software-development/how-we-built-a-notion-backup-tool-in-3-days-with-pythonvue-and-why.md",[66971],{"name":834,"to":720,"avatar":66972},{"src":722},{"label":837,"color":32369},{"type":725,"value":66975,"toc":67588},[66976,66983,66986,66989,66993,66996,67001,67004,67009,67012,67017,67020,67023,67027,67030,67047,67050,67054,67057,67062,67065,67070,67073,67078,67081,67086,67089,67093,67100,67106,67112,67118,67122,67153,67155,67158,67162,67199,67202,67206,67209,67234,67237,67240,67244,67248,67251,67392,67396,67403,67407,67410,67416,67418,67421,67453,67456,67460,67463,67468,67471,67476,67479,67484,67487,67489,67494,67497,67502,67505,67510,67519,67524,67527,67531,67534,67559,67562,67564,67567,67570,67573,67575,67585],[842,66977,66978,66979,66982],{},"At MusicTech Lab, we've used Notion extensively for internal documentation, project management, and knowledge sharing. But we made a strategic decision: ",[996,66980,66981],{},"we're moving away from Notion",". Not because it's a bad tool - it's excellent - but because our workflow has evolved.",[842,66984,66985],{},"We now store project data directly with our clients as a standard practice. Keeping a separate Notion workspace created fragmentation and potential security concerns. But before leaving, we needed to preserve years of accumulated knowledge.",[842,66987,66988],{},"This is the story of how we built a custom Python tool that exports Notion content to Markdown - and why we're releasing it as open source.",[863,66990,66992],{"id":66991},"why-were-leaving-notion","Why We're Leaving Notion",[842,66994,66995],{},"Our decision came down to three factors:",[842,66997,66998],{},[996,66999,67000],{},"1. Client-first data storage",[842,67002,67003],{},"We've adopted a policy of storing all project documentation directly in our clients' systems. Whether that's their GitHub, Confluence, or internal wikis - the data lives where the client can access and own it. Maintaining a parallel Notion workspace created duplication and sync headaches.",[842,67005,67006],{},[996,67007,67008],{},"2. Data ownership concerns",[842,67010,67011],{},"As a company working with music industry clients, we handle sensitive business information. Having that data in a third-party SaaS, even one as reputable as Notion, introduced unnecessary risk. Direct client storage means clearer data ownership and simpler compliance.",[842,67013,67014],{},[996,67015,67016],{},"3. Reducing tool sprawl",[842,67018,67019],{},"Every tool in your stack is a potential point of friction. By eliminating Notion and using client-native tools, we reduced context switching and simplified onboarding for new team members.",[842,67021,67022],{},"But we still had 4000+ pages of internal processes, meeting notes, and institutional knowledge that we couldn't just abandon.",[863,67024,67026],{"id":67025},"the-problem","The Problem",[842,67028,67029],{},"Notion's native export is... functional, but painful:",[958,67031,67032,67035,67038,67041,67044],{},[961,67033,67034],{},"Manual process - no automation possible",[961,67036,67037],{},"Messy folder structures with random IDs",[961,67039,67040],{},"Broken image links (Notion URLs expire)",[961,67042,67043],{},"Lost hierarchy - parent-child relationships disappear",[961,67045,67046],{},"No frontmatter for static site integration",[842,67048,67049],{},"We needed a way to extract everything in a format that would remain useful for years - clean Markdown files that could live in Git, be searched easily, and optionally published on our website.",[863,67051,67053],{"id":67052},"the-challenge","The Challenge",[842,67055,67056],{},"Building a proper Notion export tool revealed several non-obvious challenges:",[842,67058,67059],{},[996,67060,67061],{},"Hierarchical structure",[842,67063,67064],{},"Notion pages can be infinitely nested. The API returns flat lists, not trees. Preserving parent-child relationships for navigation required building our own hierarchy map.",[842,67066,67067],{},[996,67068,67069],{},"Rich content types",[842,67071,67072],{},"Notion has toggles, callouts, databases, embeds, tables, code blocks, and 20+ block types. Each needs specific Markdown conversion logic.",[842,67074,67075],{},[996,67076,67077],{},"Expiring media URLs",[842,67079,67080],{},"Notion's image and file URLs expire after approximately 1 hour. You cannot reference them directly - they must be downloaded and stored locally.",[842,67082,67083],{},[996,67084,67085],{},"Database rendering",[842,67087,67088],{},"Notion databases contain valuable structured information. They needed to be converted to readable Markdown tables with all property types preserved.",[863,67090,67092],{"id":67091},"our-solution","Our Solution",[842,67094,67095,67096,67099],{},"We built ",[895,67097,67098],{},"notion-sync",", a Python CLI tool with three main components:",[842,67101,67102,67105],{},[996,67103,67104],{},"NotionClientWrapper"," - Handles all API communication with pagination, error handling, and recursive page discovery.",[842,67107,67108,67111],{},[996,67109,67110],{},"MarkdownBuilder"," - Converts Notion blocks to clean Markdown, downloads assets locally, and generates YAML frontmatter for static site generators.",[842,67113,67114,67117],{},[996,67115,67116],{},"sync_notion.py"," - Orchestrates the process and provides a flexible CLI interface.",[1074,67119,67121],{"id":67120},"key-features","Key Features",[958,67123,67124,67130,67136,67142,67147],{},[961,67125,67126,67129],{},[996,67127,67128],{},"Recursive page crawling"," - Discovers and syncs all nested content automatically",[961,67131,67132,67135],{},[996,67133,67134],{},"Asset downloading"," - Images, PDFs, and videos are saved locally with content-based hashing",[961,67137,67138,67141],{},[996,67139,67140],{},"Hierarchy preservation"," - YAML frontmatter with parent/child relationships",[961,67143,67144,67146],{},[996,67145,67085],{}," - Converts to Markdown tables with all property types",[961,67148,67149,67152],{},[996,67150,67151],{},"Flexible CLI"," - Full sync, single page branch, database-only, and test modes",[863,67154,8484],{"id":8483},[842,67156,67157],{},"We chose simplicity over cleverness:",[1074,67159,67161],{"id":67160},"sync-tool-python","Sync Tool (Python)",[958,67163,67164,67170,67176,67181,67187,67193],{},[961,67165,67166,67169],{},[996,67167,67168],{},"Python 3.11"," - Excellent for scripting and API work",[961,67171,67172,67175],{},[996,67173,67174],{},"notion-client"," - Official Notion SDK",[961,67177,67178,67180],{},[996,67179,20251],{}," - HTTP client for asset downloading",[961,67182,67183,67186],{},[996,67184,67185],{},"PyYAML"," - Frontmatter generation",[961,67188,67189,67192],{},[996,67190,67191],{},"python-dotenv"," - Environment configuration",[961,67194,67195,67198],{},[996,67196,67197],{},"Poetry"," - Dependency management",[842,67200,67201],{},"The sync tool is under 700 lines across three files. No async complexity, no heavy frameworks - just straightforward code that's easy to understand and modify.",[1074,67203,67205],{"id":67204},"documentation-site-vue-nuxt","Documentation Site (Vue + Nuxt)",[842,67207,67208],{},"The synced Markdown files power a documentation site built with:",[958,67210,67211,67217,67222,67228],{},[961,67212,67213,67216],{},[996,67214,67215],{},"Vue 3"," - Modern reactive framework",[961,67218,67219,67221],{},[996,67220,65781],{}," - Full-stack Vue framework with excellent DX",[961,67223,67224,67227],{},[996,67225,67226],{},"Nuxt Content"," - Markdown-based CMS that reads our synced files directly",[961,67229,67230,67233],{},[996,67231,67232],{},"Nuxt UI"," - Beautiful, accessible component library",[842,67235,67236],{},"This combination is particularly powerful: Nuxt Content automatically parses our YAML frontmatter and builds navigation from the hierarchy metadata. The synced Markdown files become a fully navigable documentation site with zero additional configuration.",[842,67238,67239],{},"The Vue + Nuxt ecosystem is our go-to for content-heavy sites. It's fast, SEO-friendly, and the developer experience is outstanding.",[863,67241,67243],{"id":67242},"obstacles-we-overcame","Obstacles We Overcame",[1074,67245,67247],{"id":67246},"recursive-block-fetching","Recursive Block Fetching",[842,67249,67250],{},"Notion's API returns blocks without their children. We implemented recursive fetching:",[1013,67252,67254],{"className":1368,"code":67253,"language":1250,"meta":728,"style":728},"def process_blocks_recursive(blocks_list, indent=0):\n    for block in blocks_list:\n        builder.process_block(block, indent)\n\n        if block.get(\"has_children\", False):\n            child_blocks = notion_client.get_blocks(block[\"id\"])\n            process_blocks_recursive(child_blocks, indent + 1)\n",[895,67255,67256,67278,67292,67313,67317,67342,67371],{"__ignoreMap":728},[1086,67257,67258,67260,67263,67265,67268,67270,67272,67274,67276],{"class":1088,"line":1089},[1086,67259,1392],{"class":1155},[1086,67261,67262],{"class":1105}," process_blocks_recursive",[1086,67264,1398],{"class":1146},[1086,67266,67267],{"class":1401},"blocks_list",[1086,67269,1227],{"class":1146},[1086,67271,1478],{"class":1401},[1086,67273,1440],{"class":1146},[1086,67275,4417],{"class":1187},[1086,67277,4047],{"class":1146},[1086,67279,67280,67282,67285,67287,67290],{"class":1088,"line":729},[1086,67281,5925],{"class":1423},[1086,67283,67284],{"class":1436}," block ",[1086,67286,5931],{"class":1423},[1086,67288,67289],{"class":1436}," blocks_list",[1086,67291,1418],{"class":1146},[1086,67293,67294,67297,67299,67302,67304,67307,67309,67311],{"class":1088,"line":1112},[1086,67295,67296],{"class":1436},"        builder",[1086,67298,861],{"class":1146},[1086,67300,67301],{"class":1105},"process_block",[1086,67303,1398],{"class":1146},[1086,67305,67306],{"class":1105},"block",[1086,67308,1227],{"class":1146},[1086,67310,1478],{"class":1105},[1086,67312,1455],{"class":1146},[1086,67314,67315],{"class":1088,"line":1181},[1086,67316,3390],{"emptyLinePlaceholder":738},[1086,67318,67319,67321,67324,67326,67328,67330,67332,67335,67337,67339],{"class":1088,"line":1205},[1086,67320,6800],{"class":1423},[1086,67322,67323],{"class":1436}," block",[1086,67325,861],{"class":1146},[1086,67327,10812],{"class":1105},[1086,67329,1398],{"class":1146},[1086,67331,1159],{"class":1146},[1086,67333,67334],{"class":1096},"has_children",[1086,67336,1159],{"class":1146},[1086,67338,1227],{"class":1146},[1086,67340,67341],{"class":1146}," False):\n",[1086,67343,67344,67347,67349,67352,67354,67357,67359,67361,67363,67365,67367,67369],{"class":1088,"line":1276},[1086,67345,67346],{"class":1436},"            child_blocks ",[1086,67348,1440],{"class":1146},[1086,67350,67351],{"class":1436}," notion_client",[1086,67353,861],{"class":1146},[1086,67355,67356],{"class":1105},"get_blocks",[1086,67358,1398],{"class":1146},[1086,67360,67306],{"class":1105},[1086,67362,4340],{"class":1146},[1086,67364,1159],{"class":1146},[1086,67366,4156],{"class":1096},[1086,67368,1159],{"class":1146},[1086,67370,23233],{"class":1146},[1086,67372,67373,67376,67378,67381,67383,67386,67388,67390],{"class":1088,"line":1282},[1086,67374,67375],{"class":1105},"            process_blocks_recursive",[1086,67377,1398],{"class":1146},[1086,67379,67380],{"class":1105},"child_blocks",[1086,67382,1227],{"class":1146},[1086,67384,67385],{"class":1105}," indent ",[1086,67387,30708],{"class":1146},[1086,67389,23488],{"class":1187},[1086,67391,1455],{"class":1146},[1074,67393,67395],{"id":67394},"database-permission-gaps","Database Permission Gaps",[842,67397,67398,67399,67402],{},"Not all databases visible in search are actually accessible for querying. We added a ",[895,67400,67401],{},"--test-database-access"," mode that probes each database before attempting sync.",[1074,67404,67406],{"id":67405},"large-workspace-performance","Large Workspace Performance",[842,67408,67409],{},"Syncing 4000+ pages needed visibility. We added progress bars and timing breakdowns:",[1013,67411,67414],{"className":67412,"code":67413,"language":1018},[1016],"[=================>              ] 53.2% (213/400)\n\nTiming breakdown:\n   Collecting pages:    12.3s\n   Collecting metadata: 45.2s\n   Syncing pages:       3m 21.5s\n",[895,67415,67413],{"__ignoreMap":728},[863,67417,65415],{"id":65414},[842,67419,67420],{},"After 3 days of development:",[958,67422,67423,67429,67435,67441,67447],{},[961,67424,67425,67428],{},[996,67426,67427],{},"4000+ pages synced"," in under 2 hours",[961,67430,67431,67434],{},[996,67432,67433],{},"Zero manual intervention"," - runs via console",[961,67436,67437,67440],{},[996,67438,67439],{},"Full asset backup"," - all images and files stored locally",[961,67442,67443,67446],{},[996,67444,67445],{},"Navigation-ready output"," - integrates with Nuxt Content",[961,67448,67449,67452],{},[996,67450,67451],{},"Complete database support"," - renders as Markdown tables",[842,67454,67455],{},"Our entire Notion workspace is now a Git repository of Markdown files. Searchable, versionable, and completely under our control.",[863,67457,67459],{"id":67458},"why-open-source","Why Open Source?",[842,67461,67462],{},"We're going to release this tool publicly because:",[842,67464,67465],{},[996,67466,67467],{},"1. Others face the same problem",[842,67469,67470],{},"Notion lock-in is real. Whether you're migrating to another tool, need proper backups, or want to publish content statically - the native export doesn't cut it.",[842,67472,67473],{},[996,67474,67475],{},"2. It's not our core business",[842,67477,67478],{},"We're a software development company, not a SaaS vendor. This tool solves our problem and might solve yours too. Open sourcing it costs us nothing and helps the community.",[842,67480,67481],{},[996,67482,67483],{},"3. Community improvements",[842,67485,67486],{},"The tool works for our use case but could be extended. Incremental sync, bidirectional editing, different output formats - contributions are welcome.",[863,67488,49255],{"id":25635},[842,67490,67491],{},[996,67492,67493],{},"Start with the API documentation",[842,67495,67496],{},"Notion's block structure is more complex than it looks. Reading the docs thoroughly before coding saved refactoring time.",[842,67498,67499],{},[996,67500,67501],{},"Handle expiring URLs immediately",[842,67503,67504],{},"We made downloading assets a core feature from day one. This saved us from discovering the URL expiration issue in production.",[842,67506,67507],{},[996,67508,67509],{},"Build for incremental testing",[842,67511,5119,67512,1589,67515,67518],{},[895,67513,67514],{},"--limit",[895,67516,67517],{},"--page"," flags made development fast. Testing with 5 pages instead of 400 is a massive time saver.",[842,67520,67521],{},[996,67522,67523],{},"Simple beats clever",[842,67525,67526],{},"Synchronous, straightforward code over async complexity. The sync runs nightly - saving 30 seconds with async isn't worth the debugging overhead.",[863,67528,67530],{"id":67529},"get-the-tool","Get the Tool",[842,67532,67533],{},"The tool will be available on GitHub shortly. It's designed to be self-contained:",[991,67535,67536,67544,67547,67553],{},[961,67537,67538,67539],{},"Create a Notion integration at ",[846,67540,67543],{"href":67541,"rel":67542},"https://www.notion.so/my-integrations",[850],"notion.so/my-integrations",[961,67545,67546],{},"Share your root pages with the integration",[961,67548,67549,67550,67552],{},"Set up ",[895,67551,1622],{}," with your API key and root page IDs",[961,67554,67555,67556],{},"Run ",[895,67557,67558],{},"poetry run notion-sync",[842,67560,67561],{},"Watch our GitHub for the release announcement.",[863,67563,18681],{"id":18680},[842,67565,67566],{},"Leaving a tool like Notion after years of use is daunting. The fear of losing institutional knowledge is real. But with the right migration tooling, it becomes manageable.",[842,67568,67569],{},"Our new approach - storing documentation directly with clients - is cleaner and more secure. The Notion export tool ensured we didn't lose anything in the transition.",[842,67571,67572],{},"If you're considering a similar move, or just want reliable Notion backups, the tool is there for you. Fork it, adapt it, contribute back.",[4937,67574],{},[842,67576,67577],{},[964,67578,67579,67580,67584],{},"Building developer tools or planning a platform migration? ",[846,67581,67583],{"href":4946,"rel":67582},[850],"Contact MusicTech Lab"," - we help companies solve complex technical challenges in the music industry.",[1680,67586,67587],{},"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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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);}",{"title":728,"searchDepth":729,"depth":729,"links":67589},[67590,67591,67592,67593,67596,67600,67605,67606,67607,67608,67609],{"id":66991,"depth":729,"text":66992},{"id":67025,"depth":729,"text":67026},{"id":67052,"depth":729,"text":67053},{"id":67091,"depth":729,"text":67092,"children":67594},[67595],{"id":67120,"depth":1112,"text":67121},{"id":8483,"depth":729,"text":8484,"children":67597},[67598,67599],{"id":67160,"depth":1112,"text":67161},{"id":67204,"depth":1112,"text":67205},{"id":67242,"depth":729,"text":67243,"children":67601},[67602,67603,67604],{"id":67246,"depth":1112,"text":67247},{"id":67394,"depth":1112,"text":67395},{"id":67405,"depth":1112,"text":67406},{"id":65414,"depth":729,"text":65415},{"id":67458,"depth":729,"text":67459},{"id":25635,"depth":729,"text":49255},{"id":67529,"depth":729,"text":67530},{"id":18680,"depth":729,"text":18681},"2024-12-23T00:00:00.000Z","How we built an automated Notion-to-Markdown sync tool in 3 days, why we left Notion, and why we open-sourced the solution.",{"src":67613},"/images/blog/musictechlab_blog_notion-to-markdown-sync-tool.webp",{"enabled":738,"items":67615},[67616,67618,67620,67622],{"text":67617,"icon":2939},"4,000+ Notion pages were synced to Markdown in under 2 hours with a custom Python tool.",{"text":67619,"icon":3271},"Notion image URLs expire after about 1 hour, so assets must be downloaded and stored locally.",{"text":67621,"icon":5365},"Synchronous, simple code beat async complexity for a nightly batch job under 700 lines.",{"text":67623,"icon":7498},"Storing docs in client systems eliminated Notion lock-in and simplified data ownership.",{},{"title":530,"description":67611},[18784,15279],"IqeNc2wDpu3TeOV8wg-_W6c8_4kq4uW5rbHzkU-5i6Q",{"id":67629,"title":394,"authors":67630,"badge":67633,"body":67634,"category":756,"client":723,"date":68849,"description":68850,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":68851,"keyTakeaways":68853,"meta":68863,"navigation":738,"path":395,"seo":68864,"status":723,"stem":396,"tags":68865,"teaser":723,"__hash__":68866},"posts/blog/software-development/building-a-diy-midi-controller-for-ableton-live-with-arduino.md",[67631],{"name":834,"to":720,"avatar":67632},{"src":722},{"label":837,"color":32369},{"type":725,"value":67635,"toc":68813},[67636,67638,67641,67644,67651,67653,67657,67664,67678,67685,67691,67695,67698,67705,67707,67711,67717,67774,67777,67780,67782,67786,67837,67840,67842,67846,67849,67856,67862,67865,67890,67894,67946,67949,67951,67955,67958,68268,68270,68274,68278,68293,68296,68316,68319,68323,68326,68355,68366,68369,68373,68403,68406,68408,68412,68416,68419,68423,68447,68451,68469,68472,68476,68479,68490,68493,68495,68499,68502,68506,68531,68535,68564,68568,68583,68587,68590,68646,68648,68650,68654,68665,68669,68680,68684,68692,68735,68737,68739,68742,68745,68765,68773,68776,68778,68780,68810],[863,67637,27940],{"id":27939},[842,67639,67640],{},"As music production becomes increasingly software-based, the desire for tactile, physical control remains strong among producers and musicians. There's something satisfying about turning a real knob or pressing a physical button rather than clicking with a mouse.",[842,67642,67643],{},"Commercial MIDI controllers can be expensive, and they often come with features you don't need or lack the specific controls you want. What if you could build your own custom MIDI controller tailored to your exact workflow?",[842,67645,67646,67647,67650],{},"In this article, we'll walk through building a ",[996,67648,67649],{},"DIY MIDI Volume Controller for Ableton Live"," using an Arduino Leonardo. This proof-of-concept demonstrates the fundamentals of hardware-to-DAW communication and serves as a foundation for more complex custom controllers.",[4937,67652],{},[863,67654,67656],{"id":67655},"what-were-building","What We're Building",[842,67658,67659,67660,67663],{},"Our PoC is a simple but functional ",[996,67661,67662],{},"physical volume controller"," with two buttons:",[958,67665,67666,67672],{},[961,67667,67668,67671],{},[996,67669,67670],{},"Volume Up"," - Increases the track volume",[961,67673,67674,67677],{},[996,67675,67676],{},"Volume Down"," - Decreases the track volume",[842,67679,67680,67681,67684],{},"But we're not just building a basic button controller. We've implemented ",[996,67682,67683],{},"dynamic acceleration"," - the longer you hold a button, the faster the volume changes. This creates a natural, intuitive control feel that mirrors how you'd expect a physical device to behave.",[842,67686,67687],{},[1027,67688],{"alt":67689,"src":67690},"Arduino MIDI Controller connected to Ableton Live","/images/blog/musictechlab_blog_arduino-midi-ableton-controller-screenshot.webp",[1074,67692,67694],{"id":67693},"video-demonstration","Video Demonstration",[842,67696,67697],{},"See the controller in action:",[5583,67699],{"width":67700,"height":67701,"src":67702,"title":67703,"frameBorder":4417,"allow":5588,"allowFullScreen":738,"style":67704},"100%",400,"https://www.youtube.com/embed/LP9jD-u7nt4","DIY MIDI Controller for Ableton Live with Arduino","border-radius: 12px; margin: 1rem 0;",[4937,67706],{},[863,67708,67710],{"id":67709},"why-arduino-leonardo","Why Arduino Leonardo?",[842,67712,67713,67714,861],{},"Not all Arduino boards are created equal when it comes to MIDI. The key requirement is ",[996,67715,67716],{},"native USB support",[871,67718,67719,67732],{},[874,67720,67721],{},[877,67722,67723,67726,67729],{},[880,67724,67725],{},"Board",[880,67727,67728],{},"Native USB",[880,67730,67731],{},"MIDI Support",[887,67733,67734,67745,67754,67765],{},[877,67735,67736,67739,67742],{},[892,67737,67738],{},"Arduino Leonardo",[892,67740,67741],{},"Yes",[892,67743,67744],{},"Direct USB-MIDI",[877,67746,67747,67750,67752],{},[892,67748,67749],{},"Arduino Micro",[892,67751,67741],{},[892,67753,67744],{},[877,67755,67756,67759,67762],{},[892,67757,67758],{},"Arduino Uno",[892,67760,67761],{},"No",[892,67763,67764],{},"Requires serial-to-MIDI bridge",[877,67766,67767,67770,67772],{},[892,67768,67769],{},"Arduino Mega",[892,67771,67761],{},[892,67773,67764],{},[842,67775,67776],{},"The Arduino Leonardo (and Micro) use the ATmega32U4 chip, which has built-in USB communication. This allows the board to appear as a native USB-MIDI device to your computer - no additional software or drivers needed.",[842,67778,67779],{},"When you plug in your Leonardo, your computer sees it as a MIDI device, just like any commercial controller.",[4937,67781],{},[863,67783,67785],{"id":67784},"components-youll-need","Components You'll Need",[871,67787,67788,67796],{},[874,67789,67790],{},[877,67791,67792,67794],{},[880,67793,37425],{},[880,67795,3021],{},[887,67797,67798,67805,67813,67821,67829],{},[877,67799,67800,67802],{},[892,67801,67738],{},[892,67803,67804],{},"Microcontroller with native USB-MIDI",[877,67806,67807,67810],{},[892,67808,67809],{},"2x Push Buttons",[892,67811,67812],{},"Volume up/down controls",[877,67814,67815,67818],{},[892,67816,67817],{},"USB Cable",[892,67819,67820],{},"Power and data connection",[877,67822,67823,67826],{},[892,67824,67825],{},"Breadboard (optional)",[892,67827,67828],{},"For prototyping",[877,67830,67831,67834],{},[892,67832,67833],{},"Jumper Wires",[892,67835,67836],{},"Connections",[842,67838,67839],{},"Total cost: approximately $20-30 depending on where you source components.",[4937,67841],{},[863,67843,67845],{"id":67844},"understanding-midi-control-change-messages","Understanding MIDI Control Change Messages",[842,67847,67848],{},"Before diving into the code, let's understand how MIDI volume control works.",[842,67850,67851,67852,67855],{},"MIDI uses ",[996,67853,67854],{},"Control Change (CC)"," messages to transmit parameter changes. The format is:",[1013,67857,67860],{"className":67858,"code":67859,"language":1018},[1016],"[Status Byte] [Controller Number] [Value]\n    0xBn           0-127          0-127\n",[895,67861,67859],{"__ignoreMap":728},[842,67863,67864],{},"Where:",[958,67866,67867,67879,67885],{},[961,67868,67869,27851,67872,18486,67875,67878],{},[996,67870,67871],{},"Status Byte",[895,67873,67874],{},"0xB0",[895,67876,67877],{},"0xBF"," (Control Change on channels 1-16)",[961,67880,67881,67884],{},[996,67882,67883],{},"Controller Number",": Identifies which parameter (7 = Volume)",[961,67886,67887,67889],{},[996,67888,7844],{},": The parameter value (0-127)",[1074,67891,67893],{"id":67892},"standard-midi-cc-numbers","Standard MIDI CC Numbers",[871,67895,67896,67906],{},[874,67897,67898],{},[877,67899,67900,67903],{},[880,67901,67902],{},"CC Number",[880,67904,67905],{},"Parameter",[887,67907,67908,67915,67924,67931,67939],{},[877,67909,67910,67912],{},[892,67911,4434],{},[892,67913,67914],{},"Modulation Wheel",[877,67916,67917,67919],{},[892,67918,25810],{},[892,67920,67921],{},[996,67922,67923],{},"Volume",[877,67925,67926,67928],{},[892,67927,7284],{},[892,67929,67930],{},"Pan",[877,67932,67933,67936],{},[892,67934,67935],{},"11",[892,67937,67938],{},"Expression",[877,67940,67941,67943],{},[892,67942,22238],{},[892,67944,67945],{},"Sustain Pedal",[842,67947,67948],{},"For our volume controller, we'll send CC #7 messages.",[4937,67950],{},[863,67952,67954],{"id":67953},"the-code","The Code",[842,67956,67957],{},"Here's the complete Arduino sketch:",[1013,67959,67963],{"className":67960,"code":67961,"language":67962,"meta":728,"style":728},"language-cpp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","#include \"MIDIUSB.h\"\n\n// Define button pins\nconst int VOL_UP_PIN = 2;\nconst int VOL_DOWN_PIN = 3;\n\n// Volume state\nint volumeLevel = 64; // Start at mid-level (0-127)\n\n// Variables for dynamic delay\nunsigned long pressStartTime = 0;\nunsigned long currentDelay = 100;\n\nvoid setup() {\n  pinMode(VOL_UP_PIN, INPUT_PULLUP);\n  pinMode(VOL_DOWN_PIN, INPUT_PULLUP);\n}\n\nvoid loop() {\n  // Volume Up Button Logic\n  if (digitalRead(VOL_UP_PIN) == LOW) {\n    handleButtonPress(5);\n  } else if (digitalRead(VOL_UP_PIN) == HIGH) {\n    resetDelay();\n  }\n\n  // Volume Down Button Logic\n  if (digitalRead(VOL_DOWN_PIN) == LOW) {\n    handleButtonPress(-5);\n  } else if (digitalRead(VOL_DOWN_PIN) == HIGH) {\n    resetDelay();\n  }\n}\n\nvoid handleButtonPress(int direction) {\n  unsigned long currentTime = millis();\n\n  // Speed up if button is held longer\n  if (pressStartTime == 0) {\n    pressStartTime = currentTime;\n  } else if (currentTime - pressStartTime > 500) {\n    currentDelay = max(20, currentDelay - 20);\n  }\n\n  volumeLevel = constrain(volumeLevel + direction, 0, 127);\n  sendVolumeChange(volumeLevel);\n  delay(currentDelay);\n}\n\nvoid resetDelay() {\n  pressStartTime = 0;\n  currentDelay = 200;\n}\n\nvoid sendVolumeChange(int volume) {\n  midiEventPacket_t volumeChange;\n\n  volumeChange.header = 0x0B;    // Control Change\n  volumeChange.byte1 = 0xB0;     // Channel 1\n  volumeChange.byte2 = 7;        // Volume CC\n  volumeChange.byte3 = volume;   // Value\n\n  MidiUSB.sendMIDI(volumeChange);\n  MidiUSB.flush();\n}\n","cpp",[895,67964,67965,67970,67974,67979,67984,67989,67993,67998,68003,68007,68012,68017,68022,68026,68031,68036,68041,68045,68049,68054,68059,68064,68069,68074,68079,68083,68087,68092,68097,68102,68107,68111,68115,68119,68123,68128,68133,68137,68142,68147,68152,68157,68162,68166,68170,68175,68180,68185,68189,68193,68198,68203,68208,68212,68216,68221,68226,68230,68235,68240,68245,68250,68254,68259,68264],{"__ignoreMap":728},[1086,67966,67967],{"class":1088,"line":1089},[1086,67968,67969],{},"#include \"MIDIUSB.h\"\n",[1086,67971,67972],{"class":1088,"line":729},[1086,67973,3390],{"emptyLinePlaceholder":738},[1086,67975,67976],{"class":1088,"line":1112},[1086,67977,67978],{},"// Define button pins\n",[1086,67980,67981],{"class":1088,"line":1181},[1086,67982,67983],{},"const int VOL_UP_PIN = 2;\n",[1086,67985,67986],{"class":1088,"line":1205},[1086,67987,67988],{},"const int VOL_DOWN_PIN = 3;\n",[1086,67990,67991],{"class":1088,"line":1276},[1086,67992,3390],{"emptyLinePlaceholder":738},[1086,67994,67995],{"class":1088,"line":1282},[1086,67996,67997],{},"// Volume state\n",[1086,67999,68000],{"class":1088,"line":1288},[1086,68001,68002],{},"int volumeLevel = 64; // Start at mid-level (0-127)\n",[1086,68004,68005],{"class":1088,"line":2685},[1086,68006,3390],{"emptyLinePlaceholder":738},[1086,68008,68009],{"class":1088,"line":2700},[1086,68010,68011],{},"// Variables for dynamic delay\n",[1086,68013,68014],{"class":1088,"line":3398},[1086,68015,68016],{},"unsigned long pressStartTime = 0;\n",[1086,68018,68019],{"class":1088,"line":1715},[1086,68020,68021],{},"unsigned long currentDelay = 100;\n",[1086,68023,68024],{"class":1088,"line":3409},[1086,68025,3390],{"emptyLinePlaceholder":738},[1086,68027,68028],{"class":1088,"line":3415},[1086,68029,68030],{},"void setup() {\n",[1086,68032,68033],{"class":1088,"line":3421},[1086,68034,68035],{},"  pinMode(VOL_UP_PIN, INPUT_PULLUP);\n",[1086,68037,68038],{"class":1088,"line":3427},[1086,68039,68040],{},"  pinMode(VOL_DOWN_PIN, INPUT_PULLUP);\n",[1086,68042,68043],{"class":1088,"line":3433},[1086,68044,1291],{},[1086,68046,68047],{"class":1088,"line":3439},[1086,68048,3390],{"emptyLinePlaceholder":738},[1086,68050,68051],{"class":1088,"line":3444},[1086,68052,68053],{},"void loop() {\n",[1086,68055,68056],{"class":1088,"line":3450},[1086,68057,68058],{},"  // Volume Up Button Logic\n",[1086,68060,68061],{"class":1088,"line":3456},[1086,68062,68063],{},"  if (digitalRead(VOL_UP_PIN) == LOW) {\n",[1086,68065,68066],{"class":1088,"line":3462},[1086,68067,68068],{},"    handleButtonPress(5);\n",[1086,68070,68071],{"class":1088,"line":3467},[1086,68072,68073],{},"  } else if (digitalRead(VOL_UP_PIN) == HIGH) {\n",[1086,68075,68076],{"class":1088,"line":3473},[1086,68077,68078],{},"    resetDelay();\n",[1086,68080,68081],{"class":1088,"line":3479},[1086,68082,1285],{},[1086,68084,68085],{"class":1088,"line":3485},[1086,68086,3390],{"emptyLinePlaceholder":738},[1086,68088,68089],{"class":1088,"line":3491},[1086,68090,68091],{},"  // Volume Down Button Logic\n",[1086,68093,68094],{"class":1088,"line":3497},[1086,68095,68096],{},"  if (digitalRead(VOL_DOWN_PIN) == LOW) {\n",[1086,68098,68099],{"class":1088,"line":3503},[1086,68100,68101],{},"    handleButtonPress(-5);\n",[1086,68103,68104],{"class":1088,"line":3509},[1086,68105,68106],{},"  } else if (digitalRead(VOL_DOWN_PIN) == HIGH) {\n",[1086,68108,68109],{"class":1088,"line":3515},[1086,68110,68078],{},[1086,68112,68113],{"class":1088,"line":3520},[1086,68114,1285],{},[1086,68116,68117],{"class":1088,"line":3526},[1086,68118,1291],{},[1086,68120,68121],{"class":1088,"line":3531},[1086,68122,3390],{"emptyLinePlaceholder":738},[1086,68124,68125],{"class":1088,"line":3537},[1086,68126,68127],{},"void handleButtonPress(int direction) {\n",[1086,68129,68130],{"class":1088,"line":3543},[1086,68131,68132],{},"  unsigned long currentTime = millis();\n",[1086,68134,68135],{"class":1088,"line":3549},[1086,68136,3390],{"emptyLinePlaceholder":738},[1086,68138,68139],{"class":1088,"line":3555},[1086,68140,68141],{},"  // Speed up if button is held longer\n",[1086,68143,68144],{"class":1088,"line":3561},[1086,68145,68146],{},"  if (pressStartTime == 0) {\n",[1086,68148,68149],{"class":1088,"line":3567},[1086,68150,68151],{},"    pressStartTime = currentTime;\n",[1086,68153,68154],{"class":1088,"line":17749},[1086,68155,68156],{},"  } else if (currentTime - pressStartTime > 500) {\n",[1086,68158,68159],{"class":1088,"line":19843},[1086,68160,68161],{},"    currentDelay = max(20, currentDelay - 20);\n",[1086,68163,68164],{"class":1088,"line":19848},[1086,68165,1285],{},[1086,68167,68168],{"class":1088,"line":19880},[1086,68169,3390],{"emptyLinePlaceholder":738},[1086,68171,68172],{"class":1088,"line":19896},[1086,68173,68174],{},"  volumeLevel = constrain(volumeLevel + direction, 0, 127);\n",[1086,68176,68177],{"class":1088,"line":19919},[1086,68178,68179],{},"  sendVolumeChange(volumeLevel);\n",[1086,68181,68182],{"class":1088,"line":19927},[1086,68183,68184],{},"  delay(currentDelay);\n",[1086,68186,68187],{"class":1088,"line":19948},[1086,68188,1291],{},[1086,68190,68191],{"class":1088,"line":19968},[1086,68192,3390],{"emptyLinePlaceholder":738},[1086,68194,68195],{"class":1088,"line":19987},[1086,68196,68197],{},"void resetDelay() {\n",[1086,68199,68200],{"class":1088,"line":20007},[1086,68201,68202],{},"  pressStartTime = 0;\n",[1086,68204,68205],{"class":1088,"line":20013},[1086,68206,68207],{},"  currentDelay = 200;\n",[1086,68209,68210],{"class":1088,"line":20021},[1086,68211,1291],{},[1086,68213,68214],{"class":1088,"line":20051},[1086,68215,3390],{"emptyLinePlaceholder":738},[1086,68217,68218],{"class":1088,"line":20072},[1086,68219,68220],{},"void sendVolumeChange(int volume) {\n",[1086,68222,68223],{"class":1088,"line":20077},[1086,68224,68225],{},"  midiEventPacket_t volumeChange;\n",[1086,68227,68228],{"class":1088,"line":20083},[1086,68229,3390],{"emptyLinePlaceholder":738},[1086,68231,68232],{"class":1088,"line":20095},[1086,68233,68234],{},"  volumeChange.header = 0x0B;    // Control Change\n",[1086,68236,68237],{"class":1088,"line":20112},[1086,68238,68239],{},"  volumeChange.byte1 = 0xB0;     // Channel 1\n",[1086,68241,68242],{"class":1088,"line":20117},[1086,68243,68244],{},"  volumeChange.byte2 = 7;        // Volume CC\n",[1086,68246,68247],{"class":1088,"line":20139},[1086,68248,68249],{},"  volumeChange.byte3 = volume;   // Value\n",[1086,68251,68252],{"class":1088,"line":20169},[1086,68253,3390],{"emptyLinePlaceholder":738},[1086,68255,68256],{"class":1088,"line":20197},[1086,68257,68258],{},"  MidiUSB.sendMIDI(volumeChange);\n",[1086,68260,68261],{"class":1088,"line":20227},[1086,68262,68263],{},"  MidiUSB.flush();\n",[1086,68265,68266],{"class":1088,"line":20232},[1086,68267,1291],{},[4937,68269],{},[863,68271,68273],{"id":68272},"code-breakdown","Code Breakdown",[1074,68275,68277],{"id":68276},"_1-pin-configuration","1. Pin Configuration",[1013,68279,68281],{"className":67960,"code":68280,"language":67962,"meta":728,"style":728},"pinMode(VOL_UP_PIN, INPUT_PULLUP);\npinMode(VOL_DOWN_PIN, INPUT_PULLUP);\n",[895,68282,68283,68288],{"__ignoreMap":728},[1086,68284,68285],{"class":1088,"line":1089},[1086,68286,68287],{},"pinMode(VOL_UP_PIN, INPUT_PULLUP);\n",[1086,68289,68290],{"class":1088,"line":729},[1086,68291,68292],{},"pinMode(VOL_DOWN_PIN, INPUT_PULLUP);\n",[842,68294,68295],{},"We use the Arduino's internal pull-up resistors. This means:",[958,68297,68298,68308],{},[961,68299,68300,68301,68304,68305],{},"Button ",[996,68302,68303],{},"not pressed"," = ",[895,68306,68307],{},"HIGH",[961,68309,68300,68310,68304,68313],{},[996,68311,68312],{},"pressed",[895,68314,68315],{},"LOW",[842,68317,68318],{},"This simplifies wiring - we only need to connect buttons between the pin and ground.",[1074,68320,68322],{"id":68321},"_2-dynamic-speed-control","2. Dynamic Speed Control",[842,68324,68325],{},"The acceleration logic is the most interesting part:",[1013,68327,68329],{"className":67960,"code":68328,"language":67962,"meta":728,"style":728},"if (pressStartTime == 0) {\n  pressStartTime = currentTime;\n} else if (currentTime - pressStartTime > 500) {\n  currentDelay = max(20, currentDelay - 20);\n}\n",[895,68330,68331,68336,68341,68346,68351],{"__ignoreMap":728},[1086,68332,68333],{"class":1088,"line":1089},[1086,68334,68335],{},"if (pressStartTime == 0) {\n",[1086,68337,68338],{"class":1088,"line":729},[1086,68339,68340],{},"  pressStartTime = currentTime;\n",[1086,68342,68343],{"class":1088,"line":1112},[1086,68344,68345],{},"} else if (currentTime - pressStartTime > 500) {\n",[1086,68347,68348],{"class":1088,"line":1181},[1086,68349,68350],{},"  currentDelay = max(20, currentDelay - 20);\n",[1086,68352,68353],{"class":1088,"line":1205},[1086,68354,1291],{},[958,68356,68357,68360,68363],{},[961,68358,68359],{},"First 500ms: Volume changes at a steady pace (200ms delay)",[961,68361,68362],{},"After 500ms: Delay decreases by 20ms each iteration",[961,68364,68365],{},"Minimum delay: 20ms (maximum speed)",[842,68367,68368],{},"This creates a natural \"ramp-up\" feel - tap for fine adjustments, hold for quick sweeps.",[1074,68370,68372],{"id":68371},"_3-midi-message-construction","3. MIDI Message Construction",[1013,68374,68376],{"className":67960,"code":68375,"language":67962,"meta":728,"style":728},"midiEventPacket_t volumeChange;\nvolumeChange.header = 0x0B;    // USB-MIDI event type\nvolumeChange.byte1 = 0xB0;     // MIDI status: CC on channel 1\nvolumeChange.byte2 = 7;        // Controller number: Volume\nvolumeChange.byte3 = volume;   // Value: 0-127\n",[895,68377,68378,68383,68388,68393,68398],{"__ignoreMap":728},[1086,68379,68380],{"class":1088,"line":1089},[1086,68381,68382],{},"midiEventPacket_t volumeChange;\n",[1086,68384,68385],{"class":1088,"line":729},[1086,68386,68387],{},"volumeChange.header = 0x0B;    // USB-MIDI event type\n",[1086,68389,68390],{"class":1088,"line":1112},[1086,68391,68392],{},"volumeChange.byte1 = 0xB0;     // MIDI status: CC on channel 1\n",[1086,68394,68395],{"class":1088,"line":1181},[1086,68396,68397],{},"volumeChange.byte2 = 7;        // Controller number: Volume\n",[1086,68399,68400],{"class":1088,"line":1205},[1086,68401,68402],{},"volumeChange.byte3 = volume;   // Value: 0-127\n",[842,68404,68405],{},"The MIDIUSB library handles the USB protocol; we just need to format our MIDI message correctly.",[4937,68407],{},[863,68409,68411],{"id":68410},"setting-up-in-ableton-live","Setting Up in Ableton Live",[1074,68413,68415],{"id":68414},"step-1-connect-the-arduino","Step 1: Connect the Arduino",[842,68417,68418],{},"Plug in your Arduino Leonardo via USB. It will appear as a MIDI device named \"Arduino Leonardo\" (or similar).",[1074,68420,68422],{"id":68421},"step-2-configure-midi-in-ableton","Step 2: Configure MIDI in Ableton",[991,68424,68425,68435,68438],{},[961,68426,68427,68428,68431,68432],{},"Open ",[996,68429,68430],{},"Preferences"," → ",[996,68433,68434],{},"Link/Tempo/MIDI",[961,68436,68437],{},"Find \"Arduino Leonardo\" in the MIDI Ports section",[961,68439,38208,68440,1589,68443,68446],{},[996,68441,68442],{},"Track",[996,68444,68445],{},"Remote"," for the Input",[1074,68448,68450],{"id":68449},"step-3-midi-map-the-volume","Step 3: MIDI Map the Volume",[991,68452,68453,68460,68463,68466],{},[961,68454,68455,68456,68459],{},"Enter ",[996,68457,68458],{},"MIDI Map Mode"," (Cmd/Ctrl + M)",[961,68461,68462],{},"Click on the track volume fader you want to control",[961,68464,68465],{},"Press one of your buttons",[961,68467,68468],{},"Exit MIDI Map Mode",[842,68470,68471],{},"Now your physical buttons control that fader.",[1074,68473,68475],{"id":68474},"alternative-use-a-midi-track","Alternative: Use a MIDI Track",[842,68477,68478],{},"You can also route MIDI to a track:",[991,68480,68481,68484,68487],{},[961,68482,68483],{},"Create a new MIDI track",[961,68485,68486],{},"Set Input to \"Arduino Leonardo\"",[961,68488,68489],{},"Arm the track for recording",[842,68491,68492],{},"This allows you to record automation from your physical controller.",[4937,68494],{},[863,68496,68498],{"id":68497},"extending-the-project","Extending the Project",[842,68500,68501],{},"This PoC is intentionally minimal, but it demonstrates the core concepts. Here are ideas for expansion:",[1074,68503,68505],{"id":68504},"add-more-controls","Add More Controls",[1013,68507,68509],{"className":67960,"code":68508,"language":67962,"meta":728,"style":728},"// Potentiometer for continuous control\nint potValue = analogRead(A0);\nint midiValue = map(potValue, 0, 1023, 0, 127);\nsendCC(1, midiValue); // Send as Modulation\n",[895,68510,68511,68516,68521,68526],{"__ignoreMap":728},[1086,68512,68513],{"class":1088,"line":1089},[1086,68514,68515],{},"// Potentiometer for continuous control\n",[1086,68517,68518],{"class":1088,"line":729},[1086,68519,68520],{},"int potValue = analogRead(A0);\n",[1086,68522,68523],{"class":1088,"line":1112},[1086,68524,68525],{},"int midiValue = map(potValue, 0, 1023, 0, 127);\n",[1086,68527,68528],{"class":1088,"line":1181},[1086,68529,68530],{},"sendCC(1, midiValue); // Send as Modulation\n",[1074,68532,68534],{"id":68533},"multiple-channels","Multiple Channels",[1013,68536,68538],{"className":67960,"code":68537,"language":67962,"meta":728,"style":728},"// Send to different MIDI channels\nvoid sendVolumeChange(int channel, int volume) {\n  volumeChange.byte1 = 0xB0 | (channel - 1);\n  // ...\n}\n",[895,68539,68540,68545,68550,68555,68560],{"__ignoreMap":728},[1086,68541,68542],{"class":1088,"line":1089},[1086,68543,68544],{},"// Send to different MIDI channels\n",[1086,68546,68547],{"class":1088,"line":729},[1086,68548,68549],{},"void sendVolumeChange(int channel, int volume) {\n",[1086,68551,68552],{"class":1088,"line":1112},[1086,68553,68554],{},"  volumeChange.byte1 = 0xB0 | (channel - 1);\n",[1086,68556,68557],{"class":1088,"line":1181},[1086,68558,68559],{},"  // ...\n",[1086,68561,68562],{"class":1088,"line":1205},[1086,68563,1291],{},[1074,68565,68567],{"id":68566},"add-visual-feedback","Add Visual Feedback",[1013,68569,68571],{"className":67960,"code":68570,"language":67962,"meta":728,"style":728},"// LED brightness based on volume\nanalogWrite(LED_PIN, map(volumeLevel, 0, 127, 0, 255));\n",[895,68572,68573,68578],{"__ignoreMap":728},[1086,68574,68575],{"class":1088,"line":1089},[1086,68576,68577],{},"// LED brightness based on volume\n",[1086,68579,68580],{"class":1088,"line":729},[1086,68581,68582],{},"analogWrite(LED_PIN, map(volumeLevel, 0, 127, 0, 255));\n",[1074,68584,68586],{"id":68585},"rotary-encoders","Rotary Encoders",[842,68588,68589],{},"For endless rotation (like the push encoders on professional controllers), consider using rotary encoders instead of buttons:",[1013,68591,68593],{"className":67960,"code":68592,"language":67962,"meta":728,"style":728},"#include \u003CEncoder.h>\nEncoder myEnc(2, 3);\n\nvoid loop() {\n  long newPosition = myEnc.read();\n  if (newPosition != oldPosition) {\n    volumeLevel = constrain(volumeLevel + (newPosition - oldPosition), 0, 127);\n    sendVolumeChange(volumeLevel);\n    oldPosition = newPosition;\n  }\n}\n",[895,68594,68595,68600,68605,68609,68613,68618,68623,68628,68633,68638,68642],{"__ignoreMap":728},[1086,68596,68597],{"class":1088,"line":1089},[1086,68598,68599],{},"#include \u003CEncoder.h>\n",[1086,68601,68602],{"class":1088,"line":729},[1086,68603,68604],{},"Encoder myEnc(2, 3);\n",[1086,68606,68607],{"class":1088,"line":1112},[1086,68608,3390],{"emptyLinePlaceholder":738},[1086,68610,68611],{"class":1088,"line":1181},[1086,68612,68053],{},[1086,68614,68615],{"class":1088,"line":1205},[1086,68616,68617],{},"  long newPosition = myEnc.read();\n",[1086,68619,68620],{"class":1088,"line":1276},[1086,68621,68622],{},"  if (newPosition != oldPosition) {\n",[1086,68624,68625],{"class":1088,"line":1282},[1086,68626,68627],{},"    volumeLevel = constrain(volumeLevel + (newPosition - oldPosition), 0, 127);\n",[1086,68629,68630],{"class":1088,"line":1288},[1086,68631,68632],{},"    sendVolumeChange(volumeLevel);\n",[1086,68634,68635],{"class":1088,"line":2685},[1086,68636,68637],{},"    oldPosition = newPosition;\n",[1086,68639,68640],{"class":1088,"line":2700},[1086,68641,1285],{},[1086,68643,68644],{"class":1088,"line":3398},[1086,68645,1291],{},[4937,68647],{},[863,68649,63751],{"id":63750},[1074,68651,68653],{"id":68652},"arduino-not-recognized-as-midi-device","Arduino Not Recognized as MIDI Device",[958,68655,68656,68659,68662],{},[961,68657,68658],{},"Ensure you're using an Arduino Leonardo (or compatible board with native USB)",[961,68660,68661],{},"Try a different USB port",[961,68663,68664],{},"Check that MIDIUSB library is installed",[1074,68666,68668],{"id":68667},"midi-messages-not-reaching-ableton","MIDI Messages Not Reaching Ableton",[958,68670,68671,68674,68677],{},[961,68672,68673],{},"Verify MIDI ports are enabled in Ableton preferences",[961,68675,68676],{},"Check that the correct input is selected",[961,68678,68679],{},"Use a MIDI monitor tool to verify messages are being sent",[1074,68681,68683],{"id":68682},"volume-jumps-erratically","Volume Jumps Erratically",[958,68685,68686,68689],{},[961,68687,68688],{},"Check button connections for loose wires",[961,68690,68691],{},"Add debouncing if needed:",[1013,68693,68695],{"className":67960,"code":68694,"language":67962,"meta":728,"style":728},"unsigned long lastDebounceTime = 0;\nunsigned long debounceDelay = 50;\n\n// In button check:\nif ((millis() - lastDebounceTime) > debounceDelay) {\n  // Handle button\n  lastDebounceTime = millis();\n}\n",[895,68696,68697,68702,68707,68711,68716,68721,68726,68731],{"__ignoreMap":728},[1086,68698,68699],{"class":1088,"line":1089},[1086,68700,68701],{},"unsigned long lastDebounceTime = 0;\n",[1086,68703,68704],{"class":1088,"line":729},[1086,68705,68706],{},"unsigned long debounceDelay = 50;\n",[1086,68708,68709],{"class":1088,"line":1112},[1086,68710,3390],{"emptyLinePlaceholder":738},[1086,68712,68713],{"class":1088,"line":1181},[1086,68714,68715],{},"// In button check:\n",[1086,68717,68718],{"class":1088,"line":1205},[1086,68719,68720],{},"if ((millis() - lastDebounceTime) > debounceDelay) {\n",[1086,68722,68723],{"class":1088,"line":1276},[1086,68724,68725],{},"  // Handle button\n",[1086,68727,68728],{"class":1088,"line":1282},[1086,68729,68730],{},"  lastDebounceTime = millis();\n",[1086,68732,68733],{"class":1088,"line":1288},[1086,68734,1291],{},[4937,68736],{},[863,68738,18681],{"id":18680},[842,68740,68741],{},"Building a custom MIDI controller is more accessible than many producers realize. With just an Arduino Leonardo and a few components, you can create hardware perfectly suited to your workflow.",[842,68743,68744],{},"This PoC demonstrates the fundamentals:",[958,68746,68747,68753,68759],{},[961,68748,68749,68752],{},[996,68750,68751],{},"Native USB-MIDI"," communication",[961,68754,68755,68758],{},[996,68756,68757],{},"Standard MIDI CC"," messages",[961,68760,68761,68764],{},[996,68762,68763],{},"Dynamic response"," for natural control feel",[842,68766,68767,68768],{},"The complete source code is available on GitHub: ",[846,68769,68772],{"href":68770,"rel":68771},"https://github.com/musictechlab/mtl-arduino-midi-ableton",[850],"musictechlab/mtl-arduino-midi-ableton",[842,68774,68775],{},"Whether you want to add dedicated transport controls, build a custom mixing surface, or create an experimental instrument interface - the principles remain the same. Start simple, and build from there.",[4937,68777],{},[863,68779,18693],{"id":18692},[958,68781,68782,68789,68796,68803],{},[961,68783,68784],{},[846,68785,68788],{"href":68786,"rel":68787},"https://www.arduino.cc/reference/en/libraries/midiusb/",[850],"MIDIUSB Library Documentation",[961,68790,68791],{},[846,68792,68795],{"href":68793,"rel":68794},"https://midi.org/midi-1-0-control-change-messages",[850],"MIDI Association - Control Change Messages",[961,68797,68798],{},[846,68799,68802],{"href":68800,"rel":68801},"https://www.ableton.com/en/manual/midi-and-key-remote-control/",[850],"Ableton Live MIDI Mapping Guide",[961,68804,68805],{},[846,68806,68809],{"href":68807,"rel":68808},"https://docs.arduino.cc/hardware/leonardo/",[850],"Arduino Leonardo Specifications",[1680,68811,68812],{},"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);}",{"title":728,"searchDepth":729,"depth":729,"links":68814},[68815,68816,68819,68820,68821,68824,68825,68830,68836,68842,68847,68848],{"id":27939,"depth":729,"text":27940},{"id":67655,"depth":729,"text":67656,"children":68817},[68818],{"id":67693,"depth":1112,"text":67694},{"id":67709,"depth":729,"text":67710},{"id":67784,"depth":729,"text":67785},{"id":67844,"depth":729,"text":67845,"children":68822},[68823],{"id":67892,"depth":1112,"text":67893},{"id":67953,"depth":729,"text":67954},{"id":68272,"depth":729,"text":68273,"children":68826},[68827,68828,68829],{"id":68276,"depth":1112,"text":68277},{"id":68321,"depth":1112,"text":68322},{"id":68371,"depth":1112,"text":68372},{"id":68410,"depth":729,"text":68411,"children":68831},[68832,68833,68834,68835],{"id":68414,"depth":1112,"text":68415},{"id":68421,"depth":1112,"text":68422},{"id":68449,"depth":1112,"text":68450},{"id":68474,"depth":1112,"text":68475},{"id":68497,"depth":729,"text":68498,"children":68837},[68838,68839,68840,68841],{"id":68504,"depth":1112,"text":68505},{"id":68533,"depth":1112,"text":68534},{"id":68566,"depth":1112,"text":68567},{"id":68585,"depth":1112,"text":68586},{"id":63750,"depth":729,"text":63751,"children":68843},[68844,68845,68846],{"id":68652,"depth":1112,"text":68653},{"id":68667,"depth":1112,"text":68668},{"id":68682,"depth":1112,"text":68683},{"id":18680,"depth":729,"text":18681},{"id":18692,"depth":729,"text":18693},"2024-12-22T00:00:00.000Z","Build a physical MIDI volume controller for Ableton Live using Arduino Leonardo. Covers hardware setup, MIDI protocol, and USB communication.",{"src":68852},"/images/blog/musictechlab_blog_arduino-midi-ableton-controller.webp",{"enabled":738,"items":68854},[68855,68857,68859,68861],{"text":68856,"icon":5504},"Total hardware cost is roughly $20-30 for a custom MIDI controller.",{"text":68858,"icon":8500},"Arduino Leonardo has native USB-MIDI support, no extra drivers needed.",{"text":68860,"icon":2939},"Dynamic acceleration makes the longer you hold a button, the faster volume changes.",{"text":68862,"icon":9547},"Commercial controllers often lack the specific controls your workflow needs.",{},{"title":394,"description":68850},[26062,18784],"UlgJT5PD9JQivIgfnlZ0uhvgVQ38ocWhskiUz8NfTj0",{"id":68868,"title":253,"authors":68869,"badge":723,"body":68872,"category":5678,"client":723,"date":69448,"description":69449,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":69450,"keyTakeaways":723,"meta":69452,"navigation":738,"path":254,"seo":69453,"status":723,"stem":255,"tags":69454,"teaser":723,"__hash__":69455},"posts/blog/newsletter/music-industry-tech-openings-november-2024-update.md",[68870],{"name":50093,"to":50094,"avatar":68871},{"src":50096},{"type":725,"value":68873,"toc":69446},[68874,68884,68891,68901,68915,68924,68928,68938,68942,68953,68961,68972,68979,68988,68997,69006,69028,69038,69071,69081,69089,69097,69111,69125,69134,69140,69144,69158,69177,69186,69201,69212,69221,69230,69249,69259,69297,69311,69342,69351,69367,69378,69387,69393,69398,69404,69416,69426,69444],[842,68875,68876,68878,68881,68882,52337],{},[996,68877,55209],{},[846,68879,5669],{"href":55214,"rel":68880},[850]," | Careers\n",[996,68883,52307],{},[958,68885,68886,68889],{},[961,68887,68888],{},"Frontend Angular Engineer",[961,68890,55231],{},[842,68892,68893,68895,68881,68898,68900],{},[996,68894,56369],{},[846,68896,5669],{"href":56374,"rel":68897},[850],[996,68899,52307],{}," 🇺🇸 🇫🇷",[958,68902,68903,68905,68907,68909,68911,68913],{},[961,68904,54614],{},[961,68906,56409],{},[961,68908,56396],{},[961,68910,52504],{},[961,68912,55776],{},[961,68914,56406],{},[842,68916,68917,68919,68881,68922,53032],{},[996,68918,12942],{},[846,68920,5669],{"href":53022,"rel":68921},[850],[996,68923,52307],{},[958,68925,68926],{},[961,68927,58780],{},[842,68929,68930,68932,68881,68935,68937],{},[996,68931,57107],{},[846,68933,5669],{"href":57112,"rel":68934},[850],[996,68936,52307],{}," 🇨🇾",[958,68939,68940],{},[961,68941,58805],{},[842,68943,68944,68947,68881,68951,52615],{},[996,68945,68946],{},"FM",[846,68948,5669],{"href":68949,"rel":68950},"https://www.linkedin.com/company/fm-co/",[850],[996,68952,52307],{},[958,68954,68955,68958],{},[961,68956,68957],{},"PHP Engineer",[961,68959,68960],{},"React Engineer",[842,68962,68963,68966,68881,68970,52615],{},[996,68964,68965],{},"Soundful",[846,68967,5669],{"href":68968,"rel":68969},"https://www.linkedin.com/company/soundful/",[850],[996,68971,52307],{},[958,68973,68974,68976],{},[961,68975,52504],{},[961,68977,68978],{},"Data Scientist",[842,68980,68981,68983,68881,68986,52615],{},[996,68982,54468],{},[846,68984,5669],{"href":54473,"rel":68985},[850],[996,68987,52307],{},[958,68989,68990,68992,68994],{},[961,68991,52504],{},[961,68993,58997],{},[961,68995,68996],{},"Staff Data Software Engineer",[842,68998,68999,69001,68881,69004,52615],{},[996,69000,54553],{},[846,69002,5669],{"href":54558,"rel":69003},[850],[996,69005,52307],{},[958,69007,69008,69010,69013,69016,69019,69022,69025],{},[961,69009,54576],{},[961,69011,69012],{},"Sr Salesforce Marketing Cloud Engineer - AXS",[961,69014,69015],{},"Director Cybersecurity Operations",[961,69017,69018],{},"Executive Assistant - Global Technology",[961,69020,69021],{},"Sr Network/VoIP Administrator (Crypto.com Arena)",[961,69023,69024],{},"Sr. Product Manager - AXS",[961,69026,69027],{},"VP Business Systems and Technology",[842,69029,69030,69032,68881,69035,69037],{},[996,69031,5059],{},[846,69033,5669],{"href":66155,"rel":69034},[850],[996,69036,52307],{}," 🇺🇸 🇮🇳 🇲🇽",[958,69039,69040,69043,69046,69049,69051,69054,69056,69059,69062,69065,69068],{},[961,69041,69042],{},"Mobile Software Development Engineer, Podcasts Team",[961,69044,69045],{},"Digital Marketing Associate",[961,69047,69048],{},"Quality Assurance Engineer I, Music Client QA",[961,69050,56600],{},[961,69052,69053],{},"Product Marketing Manager",[961,69055,52782],{},[961,69057,69058],{},"Software Development Engineer II, ART19",[961,69060,69061],{},"System Development Engineer I",[961,69063,69064],{},"Support Engineer III",[961,69066,69067],{},"Sr. Software Development Engineer",[961,69069,69070],{},"System Development Engineer I, AMC",[842,69072,69073,69075,68881,69078,69080],{},[996,69074,54964],{},[846,69076,5669],{"href":54969,"rel":69077},[850],[996,69079,52307],{}," 🇮🇱",[958,69082,69083,69085,69087],{},[961,69084,55001],{},[961,69086,58805],{},[961,69088,58802],{},[842,69090,69091,69093,69094,69096],{},[996,69092,55672],{},"\nCareers\n",[996,69095,52307],{}," 🇵🇭 🇨🇦",[958,69098,69099,69102,69105,69107,69109],{},[961,69100,69101],{},"Desktop Systems Specialist",[961,69103,69104],{},"Jr. Desktop Systems Specialist",[961,69106,55690],{},[961,69108,55723],{},[961,69110,54623],{},[842,69112,69113,69115,52297,69118,69122,69124],{},[996,69114,55902],{},[846,69116,5669],{"href":55907,"rel":69117},[850],[846,69119,52302],{"href":69120,"rel":69121},"https://jobs.lever.co/musixmatch/",[850],[996,69123,52307],{}," 🇮🇹",[958,69126,69127,69129,69132],{},[961,69128,68978],{},[961,69130,69131],{},"Backend JavaScript Engineer",[961,69133,54147],{},[842,69135,69136,69093,69138],{},[996,69137,58421],{},[996,69139,52307],{},[958,69141,69142],{},[961,69143,58146],{},[842,69145,69146,69148,52297,69152,69155,69157],{},[996,69147,55153],{},[846,69149,5669],{"href":69150,"rel":69151},"https://www.linkedin.com/company/elevenlabsio",[850],[846,69153,52302],{"href":59189,"rel":69154},[850],[996,69156,52307],{}," 🇬🇧 🇵🇱 🇪🇸 🇩🇪",[958,69159,69160,69163,69165,69167,69170,69173,69175],{},[961,69161,69162],{},"AI Safety Engineer",[961,69164,58205],{},[961,69166,52439],{},[961,69168,69169],{},"Fullstack Engineer (BE leaning - Core)",[961,69171,69172],{},"FullStack Engineer (Frontend Leaning)",[961,69174,58208],{},[961,69176,55183],{},[842,69178,69179,69181,68881,69184,66723],{},[996,69180,58822],{},[846,69182,5669],{"href":54503,"rel":69183},[850],[996,69185,52307],{},[958,69187,69188,69191,69193,69195,69198],{},[961,69189,69190],{},"Fullstack Ruby-on-Rails Engineer",[961,69192,66732],{},[961,69194,58835],{},[961,69196,69197],{},"Backend Engineer, Payments and Payouts",[961,69199,69200],{},"Cloud Security Engineer",[842,69202,69203,69206,68881,69210,52337],{},[996,69204,69205],{},"Beatdapp",[846,69207,5669],{"href":69208,"rel":69209},"https://www.linkedin.com/company/beatdapp/",[850],[996,69211,52307],{},[958,69213,69214,69217,69219],{},[961,69215,69216],{},"Data Analyst and Labeller",[961,69218,53093],{},[961,69220,52653],{},[842,69222,69223,69225,68881,69228,52308],{},[996,69224,40953],{},[846,69226,5669],{"href":55288,"rel":69227},[850],[996,69229,52307],{},[958,69231,69232,69234,69237,69240,69243,69246],{},[961,69233,56386],{},[961,69235,69236],{},"Software QA Engineer - Internship",[961,69238,69239],{},"Android Engineer Intern",[961,69241,69242],{},"QA Analyst - Intern",[961,69244,69245],{},"Frontend Engineer Apprentice",[961,69247,69248],{},"Senior Product Manager - Partner Integration",[842,69250,69251,69253,68881,69256,69258],{},[996,69252,57251],{},[846,69254,5669],{"href":57256,"rel":69255},[850],[996,69257,52307],{}," 🇵🇱 🇬🇧 🇨🇳 🇦🇺 🇺🇸",[958,69260,69261,69264,69267,69270,69273,69275,69277,69279,69282,69285,69288,69291,69294],{},[961,69262,69263],{},"Software Engineer, Professional Software",[961,69265,69266],{},"Solutions Engineer",[961,69268,69269],{},"Senior Engineer",[961,69271,69272],{},"Sr Software Engineer in Test",[961,69274,54717],{},[961,69276,56409],{},[961,69278,57285],{},[961,69280,69281],{},"Senior Research Manager-AI/CV Innovation Lab",[961,69283,69284],{},"Senior AI Researcher - Multimodal Lab",[961,69286,69287],{},"Senior Audio AI Researcher",[961,69289,69290],{},"Senior Generative AI Researcher",[961,69292,69293],{},"Engineer",[961,69295,69296],{},"Senior Wireless Power Engineer",[842,69298,69299,69301,52297,69304,69308,69310],{},[996,69300,5070],{},[846,69302,5669],{"href":66516,"rel":69303},[850],[846,69305,52302],{"href":69306,"rel":69307},"https://spotify.com/careers",[850],[996,69309,52307],{}," 🇺🇸 🇬🇧 🇸🇪",[958,69312,69313,69316,69319,69322,69325,69328,69331,69334,69337,69340],{},[961,69314,69315],{},"Machine Learning Engineer II, Subscriptions",[961,69317,69318],{},"Backend Engineer, Subscriptions",[961,69320,69321],{},"Staff Machine Learning Engineer, Content and Catalog Management",[961,69323,69324],{},"Senior Machine Learning Engineer, Personalization - Home Music",[961,69326,69327],{},"iOS Engineer II, Core Experience",[961,69329,69330],{},"Data Engineer, Personalization",[961,69332,69333],{},"Senior Security GRC Manager, SOC 2 Compliance",[961,69335,69336],{},"Junior Backend Engineer, Content Catalog",[961,69338,69339],{},"Machine Learning Engineer II, Content Understanding",[961,69341,52653],{},[842,69343,69344,69346,68881,69349,53060],{},[996,69345,59250],{},[846,69347,5669],{"href":59253,"rel":69348},[850],[996,69350,52307],{},[958,69352,69353,69355,69357,69359,69361,69363,69365],{},[961,69354,53037],{},[961,69356,55027],{},[961,69358,59270],{},[961,69360,58997],{},[961,69362,59273],{},[961,69364,66708],{},[961,69366,59267],{},[842,69368,69369,69372,68881,69375,69377],{},[996,69370,69371],{},"Kobalt Music",[846,69373,5669],{"href":58769,"rel":69374},[850],[996,69376,52307],{}," 🇮🇪 🇬🇧",[958,69379,69380,69382,69384],{},[961,69381,58786],{},[961,69383,52376],{},[961,69385,69386],{},"Lead Platform Engineer",[842,69388,69389,69093,69391,52465],{},[996,69390,55848],{},[996,69392,52307],{},[958,69394,69395],{},[961,69396,69397],{},"Senior Software Engineer Frontend",[842,69399,69400,69093,69402,52615],{},[996,69401,54413],{},[996,69403,52307],{},[958,69405,69406,69409,69411,69414],{},[961,69407,69408],{},"Senior Systems Engineer - Atlassian",[961,69410,52504],{},[961,69412,69413],{},"Senior Systems Engineer, Salesforce - Customer Experience",[961,69415,54490],{},[842,69417,69418,69420,68881,69423,69425],{},[996,69419,66830],{},[846,69421,5669],{"href":66835,"rel":69422},[850],[996,69424,52307],{}," 🇩🇪 🇺🇸 🇬🇧",[958,69427,69428,69431,69434,69436,69439,69442],{},[961,69429,69430],{},"Backend Software Engineer - Authentication and Authorisation",[961,69432,69433],{},"Senior Backend (Search) Engineer",[961,69435,66856],{},[961,69437,69438],{},"Senior Data Engineer - Data Corpus",[961,69440,69441],{},"Senior Data Engineer - Search",[961,69443,66853],{},[842,69445,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":69447},[],"2024-11-29T00:00:00.000Z","Curated list of tech job openings in the music industry for November 2024. Browse roles at music startups, labels, and streaming platforms.",{"src":69451},"/images/blog/musictechlab_blog_music-industry-tech-openings-november-2024-update.webp",{},{"title":253,"description":69449},[5678,5523],"SGxQz_eLs79IJ1QlTN4t-f6UpjkAWbU5r9NY2BLb68A",{"id":69457,"title":257,"authors":69458,"badge":723,"body":69461,"category":5678,"client":723,"date":69726,"description":69727,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":69728,"keyTakeaways":723,"meta":69730,"navigation":738,"path":258,"seo":69731,"status":723,"stem":259,"tags":69732,"teaser":723,"__hash__":69733},"posts/blog/newsletter/music-industry-tech-openings-october-2024-update.md",[69459],{"name":50093,"to":50094,"avatar":69460},{"src":50096},{"type":725,"value":69462,"toc":69724},[69463,69475,69480,69490,69496,69507,69530,69543,69547,69558,69563,69576,69584,69597,69606,69617,69624,69637,69654,69666,69700,69705,69715],[842,69464,69465,69467,69471,58597],{},[996,69466,69205],{},[846,69468,5669],{"href":69469,"rel":69470},"https://www.linkedin.com/company/beatdapp",[850],[846,69472,52302],{"href":69473,"rel":69474},"https://www.linkedin.com/company/beatdapp/jobs/",[850],[958,69476,69477],{},[961,69478,69479],{},"Security Engineer",[842,69481,69482,69484,69487,59334],{},[996,69483,53540],{},[846,69485,5669],{"href":53545,"rel":69486},[850],[846,69488,52302],{"href":53549,"rel":69489},[850],[958,69491,69492,69494],{},[961,69493,53559],{},[961,69495,53562],{},[842,69497,69498,69500,69503,58412],{},[996,69499,56801],{},[846,69501,5669],{"href":56806,"rel":69502},[850],[846,69504,52302],{"href":69505,"rel":69506},"https://www.google.com/about/careers/applications/jobs/",[850],[958,69508,69509,69512,69515,69518,69521,69524,69527],{},[961,69510,69511],{},"Staff Software Engineer, Machine Learning, YouTube Music",[961,69513,69514],{},"Senior Visual Designer, YouTube Music and Podcasts UX",[961,69516,69517],{},"Software Engineer III, Google Play",[961,69519,69520],{},"Product Manager, Google Arts and Culture",[961,69522,69523],{},"Product Manager, YouTube Create",[961,69525,69526],{},"Software Engineer III, Mobile (iOS), YouTube Shorts Creation",[961,69528,69529],{},"Software Engineer III, Mobile (Android), YouTube Shorts Creation",[842,69531,69532,69535,69539,59334],{},[996,69533,69534],{},"Lissen",[846,69536,5669],{"href":69537,"rel":69538},"https://www.linkedin.com/company/lissen/",[850],[846,69540,52302],{"href":69541,"rel":69542},"https://www.linkedin.com/company/lissen/jobs/",[850],[958,69544,69545],{},[961,69546,53340],{},[842,69548,69549,69551,69554,59334],{},[996,69550,53887],{},[846,69552,5669],{"href":53892,"rel":69553},[850],[846,69555,52302],{"href":69556,"rel":69557},"https://www.linkedin.com/company/mirelo-ai/jobs/",[850],[958,69559,69560],{},[961,69561,69562],{},"AI Research Scientist or Engineer",[842,69564,69565,69568,69572,58412],{},[996,69566,69567],{},"Napster",[846,69569,5669],{"href":69570,"rel":69571},"https://www.linkedin.com/company/napster/",[850],[846,69573,52302],{"href":69574,"rel":69575},"https://apply.workable.com/napster/",[850],[958,69577,69578,69581],{},[961,69579,69580],{},"Lead Data Scientist",[961,69582,69583],{},"InfoSec Engineer",[842,69585,69586,69589,69593,58412],{},[996,69587,69588],{},"POSH",[846,69590,5669],{"href":69591,"rel":69592},"https://www.linkedin.com/company/poshgroupnyc/",[850],[846,69594,52302],{"href":69595,"rel":69596},"https://jobs.polymer.co/posh",[850],[958,69598,69599,69602,69604],{},[961,69600,69601],{},"Lead Data Analytics Engineer",[961,69603,55084],{},[961,69605,52504],{},[842,69607,69608,69610,69614,58690],{},[996,69609,53570],{},[846,69611,5669],{"href":69612,"rel":69613},"https://www.linkedin.com/company/roli/",[850],[846,69615,52302],{"href":53579,"rel":69616},[850],[958,69618,69619,69622],{},[961,69620,69621],{},"Contract Junior Sound Designer",[961,69623,53093],{},[842,69625,69626,69629,69633,58899],{},[996,69627,69628],{},"SonyAI",[846,69630,5669],{"href":69631,"rel":69632},"https://www.linkedin.com/company/sonyai/",[850],[846,69634,52302],{"href":69635,"rel":69636},"https://ai.sony/joinus/jobroles/",[850],[958,69638,69639,69642,69645,69648,69651],{},[961,69640,69641],{},"Internship on Audio Processing and Machine Learning",[961,69643,69644],{},"Research Intern for Vision Foundation Model and Generative AI",[961,69646,69647],{},"Research Intern for Deep Generative Modeling",[961,69649,69650],{},"Research Intern for 3D/4D Generation and Perception Foundation Model",[961,69652,69653],{},"Research Intern for Imaging and Sensing (Computer Vision)",[842,69655,69656,69658,69662,69665],{},[996,69657,53601],{},[846,69659,5669],{"href":69660,"rel":69661},"https://www.linkedin.com/company/focusrite-plc/",[850],[846,69663,52302],{"href":53610,"rel":69664},[850],"\nLocation(s): 🇨🇳🇬🇧🇩🇪",[958,69667,69668,69671,69674,69677,69680,69683,69686,69689,69692,69695,69698],{},[961,69669,69670],{},"Technical Support Engineer (APAC)",[961,69672,69673],{},"Software Engineering Manager",[961,69675,69676],{},"Qt and C++ Developer - TiMax",[961,69678,69679],{},"IT System Administrator (Microsoft) ADAM Audio",[961,69681,69682],{},"Software Developer Placement - Linea Research",[961,69684,69685],{},"Technical Support Engineer Placement",[961,69687,69688],{},"Production Systems Developer Placement",[961,69690,69691],{},"Hardware (Electronics and Firmware) Placement",[961,69693,69694],{},"Embedded Software Placement",[961,69696,69697],{},"Software Developer (C++) Placement",[961,69699,56329],{},[842,69701,69702],{},[996,69703,69704],{},"Soundtrack Your Brand",[842,69706,69707,69711,59259],{},[846,69708,5669],{"href":69709,"rel":69710},"https://www.linkedin.com/company/soundtrackyourbrand/",[850],[846,69712,52302],{"href":69713,"rel":69714},"https://careers.soundtrackyourbrand.com/",[850],[958,69716,69717,69719,69721],{},[961,69718,52439],{},[961,69720,55027],{},[961,69722,69723],{},"App Engineer (Music Experience)",{"title":728,"searchDepth":729,"depth":729,"links":69725},[],"2024-10-21T00:00:00.000Z","Curated list of tech job openings in the music industry for October 2024. Explore roles at music startups, labels, and MusicTech companies.",{"src":69729},"/images/blog/musictechlab_blog_music-industry-tech-openings-october-2024-update.webp",{},{"title":257,"description":69727},[5678,5523],"jYoNxx9Ze_XMcfC2M5SsfDIjFt6GJqhhwedn1Czmnmg",{"id":69735,"title":550,"authors":69736,"badge":69739,"body":69740,"category":756,"client":723,"date":69838,"description":69839,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":69840,"keyTakeaways":69842,"meta":69850,"navigation":738,"path":551,"seo":69851,"status":723,"stem":552,"tags":69852,"teaser":723,"__hash__":69853},"posts/blog/software-development/introducing-musictech-poland.md",[69737],{"name":50093,"to":50094,"avatar":69738},{"src":50096},{"label":50098,"color":50099},{"type":725,"value":69741,"toc":69831},[69742,69745,69749,69752,69758,69763,69767,69776,69780,69783,69787,69790,69794,69809,69814,69818],[842,69743,69744],{},"MusicTech Poland has officially launched — a community initiative that unites startups, music professionals, artists, and anyone working at the intersection of music and technology. The goal is to build a space where tech innovators and the music industry can connect, learn, and grow together.",[863,69746,69748],{"id":69747},"vision","Vision",[842,69750,69751],{},"The focus is on boosting Poland's music sector with tech advancements and fostering collaboration between creators and the industry. Through online and in-person events, MusicTech Poland encourages shared learning and fresh ideas.",[842,69753,69754],{},[1027,69755],{"alt":69756,"src":69757},"MusicTech Poland Meetup","/images/cdn-migrated/musictech-poland-meetup-3.webp",[842,69759,69760],{},[964,69761,69762],{},"Photo: MusicTech Poland",[863,69764,69766],{"id":69765},"expanding-polands-global-role","Expanding Poland's Global Role",[842,69768,69769,69770,69775],{},"MusicTech Poland aims to raise Poland's profile in the global music and tech scene — participating in international events and collaborating with organisations like ",[846,69771,69774],{"href":69772,"rel":69773},"https://musictech.eu/",[850],"Music Tech Europe",", which links similar initiatives across the continent.",[863,69777,69779],{"id":69778},"key-areas","Key Areas",[842,69781,69782],{},"The initiative covers data, AI, music distribution, live events, VR, and ticketing — making the industry more dynamic and profitable for everyone involved. Programs like accelerators and tech labs will support new projects and ideas in Poland.",[863,69784,69786],{"id":69785},"activities","Activities",[842,69788,69789],{},"Planned activities include meetups, industry reports, job listings, and international projects. The MusicTech Poland WhatsApp group offers a space for ongoing discussions and networking.",[863,69791,69793],{"id":69792},"the-founders","The Founders",[842,69795,69796,69797,1589,69800,69803,69804,861],{},"MusicTech Poland was co-founded by ",[846,69798,834],{"href":720,"rel":69799},[850],[846,69801,50093],{"href":50094,"rel":69802},[850]," from MusicTech Lab, along with ",[846,69805,69808],{"href":69806,"rel":69807},"https://www.linkedin.com/in/aga-samitowska/",[850],"Aga Samitowska",[842,69810,69811],{},[1027,69812],{"alt":69756,"src":69813},"/images/cdn-migrated/musitech-poland-meetup-1.webp",[842,69815,69816],{},[964,69817,69762],{},[1045,69819,69821,69825,69828],{"className":69820},[13033,50238,50239,1052],[50241,69822],{"color":50243,"label":69823,"target":50245,"to":69824,"variant":50246},"Website","https://www.musictechpoland.com/",[50241,69826],{"color":50249,"label":5669,"target":50245,"to":69827,"variant":50246},"https://www.linkedin.com/company/musictech-poland/",[50241,69829],{"color":50249,"label":5665,"target":50245,"to":69830,"variant":50246},"https://www.instagram.com/musictechpoland/",{"title":728,"searchDepth":729,"depth":729,"links":69832},[69833,69834,69835,69836,69837],{"id":69747,"depth":729,"text":69748},{"id":69765,"depth":729,"text":69766},{"id":69778,"depth":729,"text":69779},{"id":69785,"depth":729,"text":69786},{"id":69792,"depth":729,"text":69793},"2024-10-15T00:00:00.000Z","MusicTech Poland launches to connect startups, artists, and tech enthusiasts. Building a community where music and technology innovation can thrive together.",{"src":69841},"/images/blog/musictechlab_blog_introducing-musictech-poland.webp",{"enabled":738,"items":69843},[69844,69846,69848],{"text":69845,"icon":9547},"MusicTech Poland connects startups, artists, and tech professionals at the music-technology intersection.",{"text":69847,"icon":4845},"Activities include meetups, industry reports, job listings, and international collaborations.",{"text":69849,"icon":1067},"The initiative covers AI, music distribution, live events, VR, and ticketing innovation in Poland.",{},{"title":550,"description":69839},[5523],"c-K-Qg9AJ_FMSRxL_PRQVwhWLg8eDH378xcAFSaPvkk",{"id":69855,"title":261,"authors":69856,"badge":723,"body":69859,"category":5678,"client":723,"date":70977,"description":70978,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":70979,"keyTakeaways":723,"meta":70981,"navigation":738,"path":262,"seo":70982,"status":723,"stem":263,"tags":70983,"teaser":723,"__hash__":70984},"posts/blog/newsletter/music-industry-tech-openings-september-2024-update.md",[69857],{"name":50093,"to":50094,"avatar":69858},{"src":50096},{"type":725,"value":69860,"toc":70934},[69861,69863,69874,69881,69883,69887,69897,69901,69903,69905,69915,69938,69940,69942,69951,69964,69966,69968,69978,70009,70011,70013,70023,70041,70043,70045,70053,70058,70060,70068,70078,70080,70082,70091,70104,70106,70110,70121,70131,70133,70137,70146,70165,70167,70171,70181,70188,70190,70192,70201,70223,70225,70227,70235,70243,70245,70247,70255,70263,70267,70277,70282,70284,70286,70295,70301,70303,70305,70315,70347,70349,70351,70361,70374,70376,70385,70404,70406,70408,70417,70425,70427,70429,70438,70442,70444,70446,70455,70459,70461,70463,70473,70511,70513,70515,70523,70533,70535,70537,70547,70559,70561,70563,70572,70582,70584,70588,70598,70603,70605,70607,70615,70619,70621,70623,70633,70645,70647,70651,70661,70675,70677,70681,70691,70695,70697,70699,70708,70731,70733,70737,70747,70751,70753,70755,70763,70767,70769,70771,70780,70793,70795,70797,70805,70818,70820,70822,70830,70843,70845,70847,70857,70892,70894,70896,70905,70915,70919,70926],[1074,69862,55602],{"id":55599},[842,69864,69865,52297,69869,69873],{},[846,69866,5669],{"href":69867,"rel":69868},"https://www.linkedin.com/company/acast-ab/",[850],[846,69870,52302],{"href":69871,"rel":69872},"https://www.acast.com/jobs",[850],"\nLocation(s): 🇸🇪 🇬🇧",[958,69875,69876,69878],{},[961,69877,52504],{},[961,69879,69880],{},"Senior Data Analyst (Business Intelligence)",[842,69882,52316],{},[1074,69884,69886],{"id":69885},"amuse","Amuse",[842,69888,69889,52297,69893,59259],{},[846,69890,5669],{"href":69891,"rel":69892},"https://www.linkedin.com/company/amuse.io/",[850],[846,69894,52302],{"href":69895,"rel":69896},"https://careers.amuse.io/jobs",[850],[958,69898,69899],{},[961,69900,52782],{},[842,69902,52316],{},[1074,69904,5059],{"id":66150},[842,69906,69907,52297,69910,69914],{},[846,69908,5669],{"href":66155,"rel":69909},[850],[846,69911,52302],{"href":69912,"rel":69913},"https://www.amazon.jobs/content/en/teams/amazon-entertainment/music",[850],"\nLocation(s): 🇺🇸 🇮🇳 🇪🇪 🇲🇽 🇩🇪",[958,69916,69917,69920,69923,69926,69929,69932,69935],{},[961,69918,69919],{},"Software Development Engineer (Ruby)",[961,69921,69922],{},"Women in Tech - Software Development Engineer, Recruiting Events",[961,69924,69925],{},"Front End Engineer",[961,69927,69928],{},"Software Development Engineer, Podcasts Visual Experience Team",[961,69930,69931],{},"Software Development Manager, Amazon Music Subscriptions Team",[961,69933,69934],{},"Senior Technical Program Manager, Digital Acceleration",[961,69936,69937],{},"Programmer Analyst, Amazon Music, Amazon Music Project & Ops-VAR",[842,69939,52316],{},[1074,69941,55633],{"id":55630},[842,69943,69944,52297,69947,69950],{},[846,69945,5669],{"href":55638,"rel":69946},[850],[846,69948,52302],{"href":55642,"rel":69949},[850],"\nLocation(s): 🇱🇧",[958,69952,69953,69955,69957,69959,69962],{},[961,69954,55651],{},[961,69956,57967],{},[961,69958,55656],{},[961,69960,69961],{},"Frontend Engineer - Internal Tooling",[961,69963,55664],{},[842,69965,52316],{},[1074,69967,56866],{"id":56863},[842,69969,69970,52297,69973,69977],{},[846,69971,5669],{"href":56871,"rel":69972},[850],[846,69974,52302],{"href":69975,"rel":69976},"https://www.apple.com/careers/pl/work-at-apple.html",[850],"\nLocation(s): 🇺🇸 🇮🇪 🇩🇪 🇨🇳 🇸🇬 🇦🇺",[958,69979,69980,69983,69986,69989,69991,69994,69997,70000,70003,70006],{},[961,69981,69982],{},"Sr. Android Engineer - Apple Music",[961,69984,69985],{},"Software Engineer - Audio & Music Apps",[961,69987,69988],{},"Music, Podcasts, Books, Fitness Localization QA Lead",[961,69990,66759],{},[961,69992,69993],{},"Software Engineer, Media Services - ASE",[961,69995,69996],{},"Senior Media Applications Engineer (Objective-C, Cocoa)",[961,69998,69999],{},"Software Engineer, Media Services",[961,70001,70002],{},"Sr. UI Engineer (Web), Pricing and Partner Monetization",[961,70004,70005],{},"Senior Product Manager - Apple Cloud Infrastructure",[961,70007,70008],{},"and more…",[842,70010,52316],{},[1074,70012,54964],{"id":54961},[842,70014,70015,52297,70019,58797],{},[846,70016,5669],{"href":70017,"rel":70018},"https://www.linkedin.com/company/art-list/?originalSubdomain=il",[850],[846,70020,52302],{"href":70021,"rel":70022},"https://www.artlistjobs.io/",[850],[958,70024,70025,70027,70030,70032,70035,70037,70039],{},[961,70026,55001],{},[961,70028,70029],{},"Core Product Analytics Team Leader",[961,70031,58802],{},[961,70033,70034],{},"Front-End Developer (React/Typescript)",[961,70036,54982],{},[961,70038,54990],{},[961,70040,54985],{},[842,70042,52316],{},[1074,70044,53942],{"id":53939},[842,70046,70047,52297,70050,59095],{},[846,70048,5669],{"href":53947,"rel":70049},[850],[846,70051,52302],{"href":53951,"rel":70052},[850],[958,70054,70055],{},[961,70056,70057],{},"Ingénieur Systèmes d'information et ERP",[1074,70059,55706],{"id":55703},[842,70061,70062,52297,70065,58412],{},[846,70063,5669],{"href":55711,"rel":70064},[850],[846,70066,52302],{"href":55715,"rel":70067},[850],[958,70069,70070,70073,70076],{},[961,70071,70072],{},"Director, Product Design",[961,70074,70075],{},"Information Security Analyst (Application Security)",[961,70077,55726],{},[842,70079,52316],{},[1074,70081,53446],{"id":53443},[842,70083,70084,52297,70087,70090],{},[846,70085,5669],{"href":53451,"rel":70086},[850],[846,70088,52302],{"href":53455,"rel":70089},[850],"\nLocation(s): 🇦🇺 🇬🇧 🏴󠁧󠁢󠁷󠁬󠁳󠁿",[958,70092,70093,70096,70098,70100,70102],{},[961,70094,70095],{},"Hybrid Product Designer/Product Manager",[961,70097,52782],{},[961,70099,58171],{},[961,70101,52504],{},[961,70103,54570],{},[842,70105,52316],{},[1074,70107,70109],{"id":70108},"audiomob","Audiomob",[842,70111,70112,52297,70116,70120],{},[846,70113,5669],{"href":70114,"rel":70115},"https://www.linkedin.com/company/audiomob/",[850],[846,70117,52302],{"href":70118,"rel":70119},"https://audiomob.io/careers",[850],"\nLocation(s): 🇦🇪",[958,70122,70123,70125,70128],{},[961,70124,55776],{},[961,70126,70127],{},"iOS Engineer (Maternity Cover)",[961,70129,70130],{},"QA Engineer (Fixed Term Contract)",[842,70132,52316],{},[1074,70134,70136],{"id":70135},"bandlab-laboratories","BandLab Laboratories",[842,70138,70139,52297,70142,70145],{},[846,70140,5669],{"href":54503,"rel":70141},[850],[846,70143,52302],{"href":54507,"rel":70144},[850],"\nLocation(s): 🇸🇬 🇵🇭",[958,70147,70148,70151,70154,70157,70160,70163],{},[961,70149,70150],{},"Middle Product Designer",[961,70152,70153],{},"Senior Backend Engineer, Ads Team",[961,70155,70156],{},"Senior Backend Engineer, Social Team",[961,70158,70159],{},"Senior Backend Engineer, Payments and Payouts Team",[961,70161,70162],{},"Fullstack Ruby-on-Rails Engineer, ReverbNation",[961,70164,58844],{},[842,70166,52316],{},[1074,70168,70170],{"id":70169},"beatbread","Beatbread",[842,70172,70173,52297,70177,58412],{},[846,70174,5669],{"href":70175,"rel":70176},"https://www.linkedin.com/company/beatbread/",[850],[846,70178,52302],{"href":70179,"rel":70180},"https://app.trinethire.com/companies/38687-beatbread/jobs",[850],[958,70182,70183,70185],{},[961,70184,52376],{},[961,70186,70187],{},"Junior Software Engineer",[842,70189,52316],{},[1074,70191,56369],{"id":56366},[842,70193,70194,52297,70197,70200],{},[846,70195,5669],{"href":56374,"rel":70196},[850],[846,70198,52302],{"href":56378,"rel":70199},[850],"\nLocation(s): 🇫🇷 🇮🇳 🇬🇧",[958,70202,70203,70206,70208,70210,70213,70215,70218,70221],{},[961,70204,70205],{},"Platform Staff Engineer",[961,70207,52504],{},[961,70209,52653],{},[961,70211,70212],{},"Junior Insights Analyst",[961,70214,54985],{},[961,70216,70217],{},"Head of QA",[961,70219,70220],{},"Business Intelligence Developer",[961,70222,56409],{},[842,70224,52316],{},[1074,70226,53137],{"id":53134},[842,70228,70229,52297,70232,58516],{},[846,70230,5669],{"href":53142,"rel":70231},[850],[846,70233,52302],{"href":53146,"rel":70234},[850],[958,70236,70237,70240],{},[961,70238,70239],{},"Tech Lead (Python)",[961,70241,70242],{},"Software Engineer (Python)",[842,70244,52316],{},[1074,70246,53630],{"id":53627},[842,70248,70249,52297,70252,59059],{},[846,70250,5669],{"href":53635,"rel":70251},[850],[846,70253,52302],{"href":53639,"rel":70254},[850],[958,70256,70257,70259,70261],{},[961,70258,53649],{},[961,70260,53652],{},[961,70262,53655],{},[1074,70264,70266],{"id":70265},"create-music-group","Create Music Group",[842,70268,70269,52297,70273,58597],{},[846,70270,5669],{"href":70271,"rel":70272},"https://www.linkedin.com/company/createmusicgroup/",[850],[846,70274,52302],{"href":70275,"rel":70276},"https://createmusicgroup.rippling-ats.com/",[850],[958,70278,70279],{},[961,70280,70281],{},"IT Support Specialist",[842,70283,52316],{},[1074,70285,52450],{"id":52447},[842,70287,70288,52297,70291,70294],{},[846,70289,5669],{"href":52455,"rel":70290},[850],[846,70292,52302],{"href":52459,"rel":70293},[850],"\nLocation(s): 🇬🇧 🇩🇪",[958,70296,70297,70299],{},[961,70298,54717],{},[961,70300,55748],{},[842,70302,52316],{},[1074,70304,66242],{"id":66241},[842,70306,70307,52297,70310,70314],{},[846,70308,5669],{"href":57256,"rel":70309},[850],[846,70311,52302],{"href":70312,"rel":70313},"https://www.dolby.com/careers/",[850],"\nLocation(s): 🇨🇳 🇵🇱 🇺🇸 🇮🇳",[958,70316,70317,70320,70323,70326,70329,70332,70334,70336,70339,70342,70345],{},[961,70318,70319],{},"Junior Application Services Administrator (Linux and Cloud Services)",[961,70321,70322],{},"Staff DevOps Engineer",[961,70324,70325],{},"Head of Experience Delivery Platform",[961,70327,70328],{},"Senior Multimodal Researcher",[961,70330,70331],{},"Sr Foundational AI Researcher (Audio)",[961,70333,55084],{},[961,70335,69263],{},[961,70337,70338],{},"Field Application Engineer",[961,70340,70341],{},"Mid/Senior Embedded Engineer",[961,70343,70344],{},"Build and Release Engineer",[961,70346,70008],{},[842,70348,52316],{},[1074,70350,55153],{"id":55150},[842,70352,70353,52297,70356,70360],{},[846,70354,5669],{"href":55158,"rel":70355},[850],[846,70357,52302],{"href":70358,"rel":70359},"https://jobs.ashbyhq.com/elevenlabs/#jobs",[850],"\nLocation(s): 🇵🇱 🇪🇸 🇬🇧 🇮🇪 🇩🇪",[958,70362,70363,70365,70367,70369,70372],{},[961,70364,58205],{},[961,70366,52439],{},[961,70368,55183],{},[961,70370,70371],{},"Full-Stack Engineer (BE leaning - Core)",[961,70373,69172],{},[1074,70375,52356],{"id":52353},[842,70377,70378,52297,70381,70384],{},[846,70379,5669],{"href":52361,"rel":70380},[850],[846,70382,52302],{"href":55959,"rel":70383},[850],"\nLocation(s): 🇬🇧 🇺🇸 🇩🇪",[958,70386,70387,70390,70393,70396,70399,70401],{},[961,70388,70389],{},"Digital Project Manager",[961,70391,70392],{},"Junior IT Support Technician",[961,70394,70395],{},"Manager, IT User Support",[961,70397,70398],{},"PreSonus Social Media & Design Coordinator",[961,70400,52409],{},[961,70402,70403],{},"Staff Software Engineer, Applications",[842,70405,52316],{},[1074,70407,53663],{"id":53660},[842,70409,70410,52297,70413,58775],{},[846,70411,5669],{"href":53668,"rel":70412},[850],[846,70414,52302],{"href":70415,"rel":70416},"https://www.inmusicbrands.com/careers",[850],[958,70418,70419,70422],{},[961,70420,70421],{},"Hardware Engineer - Moog Music",[961,70423,70424],{},"Build Engineer",[842,70426,52316],{},[1074,70428,53168],{"id":53165},[842,70430,70431,70435,58412],{},[846,70432,5669],{"href":70433,"rel":70434},"https://www.linkedin.com/company/landrmusic/?originalSubdomain=ca",[850],[846,70436,52302],{"href":53177,"rel":70437},[850],[958,70439,70440],{},[961,70441,54570],{},[842,70443,52316],{},[1074,70445,56176],{"id":56173},[842,70447,70448,70452,58690],{},[846,70449,5669],{"href":70450,"rel":70451},"https://www.linkedin.com/company/levellr/?originalSubdomain=uk",[850],[846,70453,52302],{"href":56185,"rel":70454},[850],[958,70456,70457],{},[961,70458,53340],{},[842,70460,52316],{},[1074,70462,52701],{"id":52698},[842,70464,70465,70468,70472],{},[846,70466,5669],{"href":66612,"rel":70467},[850],[846,70469,52302],{"href":70470,"rel":70471},"https://livenation.wd1.myworkdayjobs.com/LNExternalSite",[850],"\nLocation(s): 🇹🇼 🇦🇺 🇺🇸 🇬🇧 🇨🇦 🇧🇷 🇬🇷",[958,70474,70475,70478,70481,70484,70487,70490,70493,70496,70499,70502,70505,70508],{},[961,70476,70477],{},"IT Cloud Application Engineer",[961,70479,70480],{},"Senior Data Platform Engineer - NoSQL",[961,70482,70483],{},"Lead Engineer",[961,70485,70486],{},"Local IT Support Lead",[961,70488,70489],{},"Senior Manager, Data Discovery and Test",[961,70491,70492],{},"Cyber Risk M&A Facilitator",[961,70494,70495],{},"SRE Engineer",[961,70497,70498],{},"Field IT Support",[961,70500,70501],{},"Corporate IT Controls Senior Manager",[961,70503,70504],{},"Software Engineer - CRM (Salesforce Marketing Cloud)",[961,70506,70507],{},"LN Concerts, Director of Venue IT Services",[961,70509,70510],{},"and more...",[842,70512,52316],{},[1074,70514,54468],{"id":54465},[842,70516,70517,70520,58412],{},[846,70518,5669],{"href":54473,"rel":70519},[850],[846,70521,52302],{"href":54477,"rel":70522},[850],[958,70524,70525,70528,70530],{},[961,70526,70527],{},"Senior / Lead Machine Learning Engineer",[961,70529,52504],{},[961,70531,70532],{},"Staff Software Engineer (Data)",[842,70534,52316],{},[1074,70536,57107],{"id":57104},[842,70538,70539,70543,70546],{},[846,70540,5669],{"href":70541,"rel":70542},"https://www.linkedin.com/company/muse",[850],[846,70544,52302],{"href":57118,"rel":70545},[850],"\nLocation(s): 🇨🇾 🇬🇧 🇺🇸",[958,70548,70549,70551,70554,70556],{},[961,70550,70281],{},[961,70552,70553],{},"Senior Full Stack Developer (6-month contract)",[961,70555,54026],{},[961,70557,70558],{},"Frontend Lead Developer",[842,70560,52316],{},[1074,70562,55902],{"id":55899},[842,70564,70565,70568,70571],{},[846,70566,5669],{"href":55907,"rel":70567},[850],[846,70569,52302],{"href":55911,"rel":70570},[850],"\nLocation(s): 🇮🇹",[958,70573,70574,70577,70580],{},[961,70575,70576],{},"Product Manager (Services)",[961,70578,70579],{},"IT Specialist",[961,70581,54147],{},[842,70583,52316],{},[1074,70585,70587],{"id":70586},"naim-audio","Naim Audio",[842,70589,70590,70594,58690],{},[846,70591,5669],{"href":70592,"rel":70593},"https://www.linkedin.com/company/naim-audio-ltd/",[850],[846,70595,52302],{"href":70596,"rel":70597},"https://naimaudio.pinpointhq.com/#js-careers-jobs-block",[850],[958,70599,70600],{},[961,70601,70602],{},"Software Test Engineer",[842,70604,52316],{},[1074,70606,55927],{"id":55924},[842,70608,70609,70612,58412],{},[846,70610,5669],{"href":55932,"rel":70611},[850],[846,70613,52302],{"href":55936,"rel":70614},[850],[958,70616,70617],{},[961,70618,55944],{},[842,70620,52316],{},[1074,70622,55430],{"id":55427},[842,70624,70625,70628,70632],{},[846,70626,5669],{"href":55435,"rel":70627},[850],[846,70629,52302],{"href":70630,"rel":70631},"https://careers.podimo.com/",[850],"\nLocation(s): 🇩🇰 🇪🇸 🇬🇧 🇳🇱 🇱🇹 🇩🇪",[958,70634,70635,70638,70641,70643],{},[961,70636,70637],{},"Senior User Experience Researcher",[961,70639,70640],{},"Senior Product Manager, Discovery",[961,70642,53037],{},[961,70644,55656],{},[842,70646,52316],{},[1074,70648,70650],{"id":70649},"sennheiser","Sennheiser",[842,70652,70653,70657,59334],{},[846,70654,5669],{"href":70655,"rel":70656},"https://www.linkedin.com/company/sennheiser/",[850],[846,70658,52302],{"href":70659,"rel":70660},"https://sennheiser.referrals.selectminds.com/",[850],[958,70662,70663,70666,70669,70672],{},[961,70664,70665],{},"System Test Engineer Embedded Devices",[961,70667,70668],{},"Software Engineer IT",[961,70670,70671],{},"Senior UX Researcher",[961,70673,70674],{},"Audio SW Engineer",[842,70676,52316],{},[1074,70678,70680],{"id":70679},"sirius","Sirius",[842,70682,70683,70687,59334],{},[846,70684,5669],{"href":70685,"rel":70686},"https://www.linkedin.com/company/sirius-music-school/",[850],[846,70688,52302],{"href":70689,"rel":70690},"https://join.com/companies/sirius",[850],[958,70692,70693],{},[961,70694,66759],{},[842,70696,52316],{},[1074,70698,64835],{"id":66829},[842,70700,70701,70704,70707],{},[846,70702,5669],{"href":66835,"rel":70703},[850],[846,70705,52302],{"href":66839,"rel":70706},[850],"\nLocation(s): 🇩🇪 🇬🇧 🇺🇸",[958,70709,70710,70713,70716,70719,70722,70724,70726,70728],{},[961,70711,70712],{},"Director of Engineering - Recommendations",[961,70714,70715],{},"Full Stack Software Engineer - Authentication and Authorisation",[961,70717,70718],{},"Senior Backend Engineer - Creator",[961,70720,70721],{},"Senior Cloud Network Engineer",[961,70723,66856],{},[961,70725,69438],{},[961,70727,66853],{},[961,70729,70730],{},"Senior On-Premise Network Engineer",[842,70732,52316],{},[1074,70734,70736],{"id":70735},"symphonyos","SymphonyOS",[842,70738,70739,70743,58412],{},[846,70740,5669],{"href":70741,"rel":70742},"https://www.linkedin.com/company/symphonyos/",[850],[846,70744,52302],{"href":70745,"rel":70746},"https://www.symphonyos.co/hiring/full-stack-engineer",[850],[958,70748,70749],{},[961,70750,53340],{},[842,70752,52316],{},[1074,70754,56202],{"id":56199},[842,70756,70757,70760,58412],{},[846,70758,5669],{"href":56207,"rel":70759},[850],[846,70761,52302],{"href":56211,"rel":70762},[850],[958,70764,70765],{},[961,70766,56219],{},[842,70768,52316],{},[1074,70770,52942],{"id":52939},[842,70772,70773,70776,58597],{},[846,70774,5669],{"href":52947,"rel":70775},[850],[846,70777,52302],{"href":70778,"rel":70779},"https://us241.dayforcehcm.com/CandidatePortal/en-US/octavegroup",[850],[958,70781,70782,70784,70787,70790],{},[961,70783,58205],{},[961,70785,70786],{},"Software Developer Jukebox",[961,70788,70789],{},"Director, Cybersecurity",[961,70791,70792],{},"Senior MLOps Specialist",[842,70794,52316],{},[1074,70796,54126],{"id":54123},[842,70798,70799,70802,58412],{},[846,70800,5669],{"href":54131,"rel":70801},[850],[846,70803,52302],{"href":54135,"rel":70804},[850],[958,70806,70807,70810,70813,70815],{},[961,70808,70809],{},"Software Engineer, Web",[961,70811,70812],{},"Sr. Software Engineer, Full Stack",[961,70814,52623],{},[961,70816,70817],{},"Sr. Product Manager - Artist Tools",[842,70819,52316],{},[1074,70821,59138],{"id":66770},[842,70823,70824,70827,58516],{},[846,70825,5669],{"href":59141,"rel":70826},[850],[846,70828,52302],{"href":59145,"rel":70829},[850],[958,70831,70832,70835,70837,70839,70841],{},[961,70833,70834],{},"Senior iOS C++ Developer",[961,70836,55776],{},[961,70838,66790],{},[961,70840,54026],{},[961,70842,54118],{},[842,70844,52316],{},[1074,70846,58011],{"id":58008},[842,70848,70849,70852,70856],{},[846,70850,5669],{"href":58016,"rel":70851},[850],[846,70853,52302],{"href":70854,"rel":70855},"https://www.wmg.com/careers",[850],"\nLocation(s): 🇨🇦 🇺🇸 🇬🇧",[958,70858,70859,70862,70864,70866,70868,70871,70873,70875,70877,70880,70883,70885,70887,70890],{},[961,70860,70861],{},"Visual Designer",[961,70863,58034],{},[961,70865,57724],{},[961,70867,52546],{},[961,70869,70870],{},"Production Support Engineer",[961,70872,55588],{},[961,70874,58997],{},[961,70876,52376],{},[961,70878,70879],{},"Senior IT Support Manager",[961,70881,70882],{},"Service Desk Associate",[961,70884,54570],{},[961,70886,55788],{},[961,70888,70889],{},"Senior TPM",[961,70891,70510],{},[842,70893,52316],{},[1074,70895,56304],{"id":56301},[842,70897,70898,70901,58690],{},[846,70899,5669],{"href":56309,"rel":70900},[850],[846,70902,52302],{"href":70903,"rel":70904},"https://careers.yotoplay.com/",[850],[958,70906,70907,70910,70913],{},[961,70908,70909],{},"Senior Software Engineer (Full Stack)",[961,70911,70912],{},"Senior Software Engineer (Front End)",[961,70914,55664],{},[1074,70916,70918],{"id":70917},"zylia","Zylia",[842,70920,70921,70925],{},[846,70922,5669],{"href":70923,"rel":70924},"https://www.linkedin.com/company/zylia",[850],"\nCareers\nLocation(s): 🇵🇱",[958,70927,70928,70931],{},[961,70929,70930],{},"C++ Software Engineer",[961,70932,70933],{},"Digital Signal Processing Engineer",{"title":728,"searchDepth":729,"depth":729,"links":70935},[70936,70937,70938,70939,70940,70941,70942,70943,70944,70945,70946,70947,70948,70949,70950,70951,70952,70953,70954,70955,70956,70957,70958,70959,70960,70961,70962,70963,70964,70965,70966,70967,70968,70969,70970,70971,70972,70973,70974,70975,70976],{"id":55599,"depth":1112,"text":55602},{"id":69885,"depth":1112,"text":69886},{"id":66150,"depth":1112,"text":5059},{"id":55630,"depth":1112,"text":55633},{"id":56863,"depth":1112,"text":56866},{"id":54961,"depth":1112,"text":54964},{"id":53939,"depth":1112,"text":53942},{"id":55703,"depth":1112,"text":55706},{"id":53443,"depth":1112,"text":53446},{"id":70108,"depth":1112,"text":70109},{"id":70135,"depth":1112,"text":70136},{"id":70169,"depth":1112,"text":70170},{"id":56366,"depth":1112,"text":56369},{"id":53134,"depth":1112,"text":53137},{"id":53627,"depth":1112,"text":53630},{"id":70265,"depth":1112,"text":70266},{"id":52447,"depth":1112,"text":52450},{"id":66241,"depth":1112,"text":66242},{"id":55150,"depth":1112,"text":55153},{"id":52353,"depth":1112,"text":52356},{"id":53660,"depth":1112,"text":53663},{"id":53165,"depth":1112,"text":53168},{"id":56173,"depth":1112,"text":56176},{"id":52698,"depth":1112,"text":52701},{"id":54465,"depth":1112,"text":54468},{"id":57104,"depth":1112,"text":57107},{"id":55899,"depth":1112,"text":55902},{"id":70586,"depth":1112,"text":70587},{"id":55924,"depth":1112,"text":55927},{"id":55427,"depth":1112,"text":55430},{"id":70649,"depth":1112,"text":70650},{"id":70679,"depth":1112,"text":70680},{"id":66829,"depth":1112,"text":64835},{"id":70735,"depth":1112,"text":70736},{"id":56199,"depth":1112,"text":56202},{"id":52939,"depth":1112,"text":52942},{"id":54123,"depth":1112,"text":54126},{"id":66770,"depth":1112,"text":59138},{"id":58008,"depth":1112,"text":58011},{"id":56301,"depth":1112,"text":56304},{"id":70917,"depth":1112,"text":70918},"2024-09-25T00:00:00.000Z","Curated list of tech job openings in the music industry for September 2024. Find roles at music startups, labels, and streaming platforms.",{"src":70980},"/images/blog/musictechlab_blog_music-industry-tech-openings-september-2024-update.webp",{},{"title":261,"description":70978},[5678,5523],"zNPkNAbl5MKQrFifx115-nZcEcbe6WB9IZjMxzAF26U",{"id":70986,"title":634,"authors":70987,"badge":723,"body":70992,"category":756,"client":723,"date":71097,"description":71098,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":71099,"keyTakeaways":71101,"meta":71109,"navigation":738,"path":635,"seo":71110,"status":723,"stem":636,"tags":71111,"teaser":723,"__hash__":71112},"posts/blog/software-development/the-evolution-and-future-of-e-commerce-platforms.md",[70988],{"name":70989,"avatar":70990},"Jakub Martenka",{"src":70991},"/images/people/jakub-martenka.png",{"type":725,"value":70993,"toc":71087},[70994,70996,71000,71003,71005,71009,71012,71014,71018,71033,71035,71039,71042,71045,71048,71050,71054,71057,71059,71063,71066,71069,71071,71075,71078,71080,71084],[842,70995,40867],{},[863,70997,70999],{"id":70998},"market-saturation-or-a-window-for-innovation","Market Saturation or a Window for Innovation?",[842,71001,71002],{},"While the e-commerce sector may appear saturated at first glance, this is a nuanced matter. There’s a growing demand for innovative solutions, especially in the realm of customization. SaaS platforms, despite their widespread adoption, show adaptability constraints, potentially restricting business growth and evolution.",[842,71004,52316],{},[863,71006,71008],{"id":71007},"tackling-the-challenges-of-e-commerce-traffic","Tackling the Challenges of E-commerce Traffic",[842,71010,71011],{},"Consider a successful enterprise with an expansive, active user base. When such a business transitions to an online platform, a paramount question emerges: Can the prevalent open-source or SaaS platforms manage a sudden influx of traffic, ensuring low latency and scalability? Unfortunately, not all are equipped for this challenge.",[842,71013,52316],{},[863,71015,71017],{"id":71016},"a-closer-examination-of-leading-platforms","A Closer Examination of Leading Platforms",[958,71019,71020,71027,71030],{},[961,71021,71022,71023,6786],{},"Adobe Commerce: Once known as Magento, Adobe Commerce stands as a major player. Integrated with Adobe Experience Cloud, it advocates scalability. Nonetheless, its foundational architecture remains largely monolithic. This implies that while the frontend may be efficient, the database could potentially become a bottleneck, especially during high traffic, affecting the overall user experience (",[846,71024,27530],{"href":71025,"rel":71026},"https://www.portent.com/blog/analytics/research-site-speed-hurting-everyones-revenue.htm",[850],[961,71028,71029],{},"PrestaShop: Operating on the Symfony framework, PrestaShop champions flexibility, permitting businesses to integrate modules as needed. Its scalability, however, is still anchored by its monolithic structure. Consequently, during traffic surges, the system can potentially falter.",[961,71031,71032],{},"Saleor: Developed in Django, Saleor brings forth extensive customization. Yet, its adherence to a monolithic design means it's not immune to the scaling issues common in other platforms.",[842,71034,52316],{},[863,71036,71038],{"id":71037},"custom-e-commerce-the-future","Custom E-commerce: The Future?",[842,71040,71041],{},"Given these challenges, businesses face a pivotal question: Should they invest in developing a tailor-made e-commerce platform designed explicitly for high traffic loads?",[842,71043,71044],{},"Transitioning away from integrated solutions to custom-built ones is daunting. Traditional platforms aren't designed for piecemeal migration to microservices. This means businesses would have to restructure existing functionalities, incurring significant development costs - often more than building a new system, but it’s like in a car, it’s very hard to replace the engine while driving.",[842,71046,71047],{},"Furthermore, using existing solutions can stifle innovation. Future modifications, like optimizing data storage mechanisms, become near impossible, leaving businesses handcuffed to an \"as-is\" system, or hiring hordes of developers to build a protesis ecosystem around the system.",[842,71049,52316],{},[863,71051,71053],{"id":71052},"introducing-anqa-a-paradigm-shift","Introducing Anqa: A Paradigm Shift",[842,71055,71056],{},"Anqa was conceived with the intent to redefine standards. It adopts a fully modular design, aligned with the microservices paradigm. External components interface with the external environment through an API gateway, while internal modules utilize an event bus, adhering to an event-driven blueprint with consistent protocol versioning. This approach adeptly sidesteps technological debt, an inevitable issue when retrofitting platforms like PrestaShop or Magento.",[842,71058,52316],{},[863,71060,71062],{"id":71061},"redefining-performance-metrics","Redefining Performance Metrics",[842,71064,71065],{},"Anqa’s vision transcends mere modularity. Recognizing the distinct needs of different system components, it employs optimal solutions for each. For instance, in the realm of product catalogs, a hybrid data storage methodology amalgamating NoSQL with RDBMS has proven efficacious, significantly curtailing request times and elevating user experiences.",[842,71067,71068],{},"Each strategic decision within Anqa is fortified by comprehensive research, ensuring that choices are not just innovative, but also robust.",[842,71070,52316],{},[863,71072,71074],{"id":71073},"scalability-and-independence-from-cluster-providers","Scalability and Independence from cluster providers",[842,71076,71077],{},"In sculpting Anqa, scalability, and autonomy from specific Kubernetes providers emerged as cornerstones. While platforms like Amazon or Azure simplify system construction, complexities arise when migrating between providers. This is why Anqa's design emphasizes portability, ensuring seamless operability across Google, Amazon, or Azure.",[842,71079,52316],{},[863,71081,71083],{"id":71082},"final-thoughts","Final Thoughts",[842,71085,71086],{},"In subsequent discussions, we will investigate Anqa Commerce's distinctive features and advantages. With its commitment to managing voluminous traffic, guaranteeing unparalleled scalability, and optimizing operational costs, Anqa may well represent the next phase in e-commerce evolution.",{"title":728,"searchDepth":729,"depth":729,"links":71088},[71089,71090,71091,71092,71093,71094,71095,71096],{"id":70998,"depth":729,"text":70999},{"id":71007,"depth":729,"text":71008},{"id":71016,"depth":729,"text":71017},{"id":71037,"depth":729,"text":71038},{"id":71052,"depth":729,"text":71053},{"id":71061,"depth":729,"text":71062},{"id":71073,"depth":729,"text":71074},{"id":71082,"depth":729,"text":71083},"2024-09-13T00:00:00.000Z","Comparing leading e-commerce platforms like Adobe Commerce, PrestaShop, and Saleor. How microservice architecture addresses scalability and traffic challenges.",{"src":71100},"/images/blog/musictechlab_blog_the-evolution-and-future-of-e-commerce-platforms.webp",{"enabled":738,"items":71102},[71103,71105,71107],{"text":71104,"icon":3847},"Monolithic e-commerce platforms like Magento and PrestaShop struggle with high-traffic scalability.",{"text":71106,"icon":5504},"Migrating an existing monolith to microservices often costs more than building from scratch.",{"text":71108,"icon":8737},"Anqa Commerce uses a fully modular, event-driven microservice design to avoid technical debt.",{},{"title":634,"description":71098},[52276],"uSJcixL8dBwC8JetgPytlHP3ngu7wSOj-NRGsuV8Uus",{"id":71114,"title":132,"authors":71115,"badge":71118,"body":71119,"category":731,"client":723,"date":72009,"description":72010,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":72011,"keyTakeaways":72013,"meta":72023,"navigation":738,"path":133,"seo":72024,"status":723,"stem":134,"tags":72025,"teaser":723,"__hash__":72026},"posts/blog/music-data/ddex-open-source-projects-review.md",[71116],{"name":834,"to":720,"avatar":71117},{"src":722},{"label":50098,"color":50099},{"type":725,"value":71120,"toc":71995},[71121,71124,71130,71135,71137,71140,71144,71152,71156,71159,71168,71192,71201,71223,71232,71254,71263,71284,71293,71313,71322,71342,71351,71372,71381,71401,71409,71428,71437,71458,71466,71486,71495,71515,71524,71545,71554,71574,71583,71602,71611,71632,71641,71662,71671,71692,71701,71721,71730,71750,71754,71772,71776,71791,71795,71813,71817,71839,71843,71858,71862,71900,71903,71907,71913,71929,71939,71965,71969,71978,71982],[842,71122,71123],{},"In the rapidly evolving digital music industry, managing the exchange of data across different platforms and services can be a daunting task. The complexity of licensing, distribution, and rights management makes seamless data exchange essential for all players involved - artists, record labels, distributors, and digital service providers (DSPs). To address this challenge, the Digital Data Exchange (DDEX) consortium was established to standardize data formats and processes across the industry. DDEX plays a critical role in ensuring that music rights, royalties, and metadata flow smoothly across the digital ecosystem. Among its most significant contributions are its open-source projects, which provide tools and resources that allow stakeholders to easily implement DDEX standards.",[842,71125,71126],{},[1027,71127],{"alt":71128,"src":71129},"DDEX website screenshot","/images/cdn-migrated/ddex-website-screenshot.webp",[842,71131,71132],{},[964,71133,71134],{},"CREDIT: DDEX website",[863,71136,9851],{"id":9850},[842,71138,71139],{},"DDEX, founded in 2006, is a consortium of leading media companies, digital music services, and music rights organizations. It creates and maintains standards for the communication of metadata between businesses in the digital music supply chain. These standards ensure that information such as licensing details, royalties, and usage data is accurately transferred between parties involved in the production, distribution, and consumption of digital media. The core of DDEX's mission is to enable a more efficient exchange of metadata, which is crucial for tracking music rights and ensuring that artists and rights holders receive fair compensation for their work.",[863,71141,71143],{"id":71142},"open-source-in-ddex","Open Source in DDEX",[842,71145,71146,71147,71151],{},"DDEX's open-source projects allow developers and businesses to implement its standards without having to start from scratch. By making these projects available under open-source licenses, DDEX encourages collaboration, innovation, and faster adoption of standardized protocols. This approach lowers the barrier for small and large players alike, enabling the entire music industry to benefit from greater consistency and transparency in metadata management. Here is a list of Open Source tools provided by DDEX: ",[846,71148,71149],{"href":71149,"rel":71150},"https://kb.ddex.net/reference-material/open-source-software/",[850]," Let's delve inside them.",[863,71153,71155],{"id":71154},"open-source-software-from-ddex-website","Open Source Software (from DDEX website)",[842,71157,71158],{},"The DDEX website lists several open-source tools available on GitHub, developed by various companies and individuals. These tools can assist with parsing, validating, and converting DDEX-compliant metadata files in various programming languages, such as Python, Ruby, JavaScript, and PHP. Many of the tools focus on validating, parsing, and converting DDEX XML files related to music metadata, ensuring conformance to DDEX standards for digital sales reporting (DSR), Electronic Release Notification (ERN), and Metadata Choreography (MEAD).",[842,71160,71161],{},[846,71162,71165],{"href":71163,"rel":71164},"https://github.com/ddexnet/dsrf",[850],[996,71166,71167],{},"dsrf",[958,71169,71170,71175,71180,71186],{},[961,71171,71172,71174],{},[996,71173,9870],{},": DSR (Digital Sales Report)",[961,71176,71177,71179],{},[996,71178,7624],{},": The DSRF Flat File Parser and Conformance Tool is an open-source library that allows parsing and testing of DDEX DSR Flat files, ensuring they comply with DDEX DSR Flat File Standard v3.0.",[961,71181,71182,71185],{},[996,71183,71184],{},"Language",": Python",[961,71187,71188,71191],{},[996,71189,71190],{},"Creator",": Google",[842,71193,71194],{},[846,71195,71198],{"href":71196,"rel":71197},"https://github.com/sshaw/ddex",[850],[996,71199,71200],{},"ddex ruby",[958,71202,71203,71208,71213,71218],{},[961,71204,71205,71207],{},[996,71206,9870],{},": ERN (Electronic Release Notification)",[961,71209,71210,71212],{},[996,71211,7624],{},": This library facilitates DDEX metadata serialization for Ruby. Comprehensive documentation is available for easy integration.",[961,71214,71215,71217],{},[996,71216,71184],{},": Ruby",[961,71219,71220,71222],{},[996,71221,71190],{},": Skye Shaw",[842,71224,71225],{},[846,71226,71229],{"href":71227,"rel":71228},"https://github.com/7digital/DDEX-Deserialiser",[850],[996,71230,71231],{},"DDEX Deserialiser",[958,71233,71234,71239,71244,71249],{},[961,71235,71236,71238],{},[996,71237,9870],{},": ERN",[961,71240,71241,71243],{},[996,71242,7624],{},": A tool developed by 7digital for batch deserialization of DDEX XML files, allowing seamless conversion of XML data to usable formats.",[961,71245,71246,71248],{},[996,71247,71184],{},": C#",[961,71250,71251,71253],{},[996,71252,71190],{},": 7digital",[842,71255,71256],{},[846,71257,71260],{"href":71258,"rel":71259},"https://github.com/eddleston/DDEX-Validator",[850],[996,71261,71262],{},"DDEX Validator",[958,71264,71265,71269,71274,71279],{},[961,71266,71267,71238],{},[996,71268,9870],{},[961,71270,71271,71273],{},[996,71272,7624],{},": A validation service designed to verify DDEX XML documents for compliance with the DDEX ERN standard.",[961,71275,71276,71278],{},[996,71277,71184],{},": NuGet/Microsoft Visual Studio",[961,71280,71281,71283],{},[996,71282,71190],{},": Paul Eddleston",[842,71285,71286],{},[846,71287,71290],{"href":71288,"rel":71289},"https://github.com/thornlaw/ddex-ern-lib",[850],[996,71291,71292],{},"ddex-ern-lib",[958,71294,71295,71299,71304,71308],{},[961,71296,71297,71238],{},[996,71298,9870],{},[961,71300,71301,71303],{},[996,71302,7624],{},": A DDEX ERN 3.7 XML Messaging Class Library, leveraging Linq to XSD and T4 Templates, for easy handling of ERN messages.",[961,71305,71306,71248],{},[996,71307,71184],{},[961,71309,71310,71312],{},[996,71311,71190],{},": Thornlaw Consultants",[842,71314,71315],{},[846,71316,71319],{"href":71317,"rel":71318},"https://github.com/willm/DDEXUI",[850],[996,71320,71321],{},"DDEXUI (1)",[958,71323,71324,71328,71333,71337],{},[961,71325,71326,71238],{},[996,71327,9870],{},[961,71329,71330,71332],{},[996,71331,7624],{},": A user interface designed to help create simplified DDEX metadata, making the metadata creation process easier for users.",[961,71334,71335,71185],{},[996,71336,71184],{},[961,71338,71339,71341],{},[996,71340,71190],{},": Will Munn",[842,71343,71344],{},[846,71345,71348],{"href":71346,"rel":71347},"https://github.com/agreen757/SoundCloudDDEX",[850],[996,71349,71350],{},"SoundCloud DDEX",[958,71352,71353,71357,71362,71367],{},[961,71354,71355,71238],{},[996,71356,9870],{},[961,71358,71359,71361],{},[996,71360,7624],{},": A tool for generating DDEX feeds for SoundCloud by converting data from CSV files into DDEX-compliant metadata.",[961,71363,71364,71366],{},[996,71365,71184],{},": JavaScript",[961,71368,71369,71371],{},[996,71370,71190],{},": Adrian Green",[842,71373,71374],{},[846,71375,71378],{"href":71376,"rel":71377},"https://github.com/Trax-air/ddexreader",[850],[996,71379,71380],{},"DDEXreader",[958,71382,71383,71387,71392,71396],{},[961,71384,71385,71238],{},[996,71386,9870],{},[961,71388,71389,71391],{},[996,71390,7624],{},": A Python project that allows reading of DDEX XML files and decoding them into Python data types using the PyXB library.",[961,71393,71394,71185],{},[996,71395,71184],{},[961,71397,71398,71400],{},[996,71399,71190],{},": Trax Air",[842,71402,71403],{},[846,71404,71406],{"href":71317,"rel":71405},[850],[996,71407,71408],{},"DDEXUI (2)",[958,71410,71411,71415,71420,71424],{},[961,71412,71413,71238],{},[996,71414,9870],{},[961,71416,71417,71419],{},[996,71418,7624],{},": Another version of DDEXUI, focusing on making DDEX metadata creation more accessible for small labels and independent artists by simplifying the process.",[961,71421,71422,71185],{},[996,71423,71184],{},[961,71425,71426,71341],{},[996,71427,71190],{},[842,71429,71430],{},[846,71431,71434],{"href":71432,"rel":71433},"https://github.com/moodleexpert/DDEXPHPParser",[850],[996,71435,71436],{},"DDEX PHP Parser",[958,71438,71439,71443,71448,71453],{},[961,71440,71441,71238],{},[996,71442,9870],{},[961,71444,71445,71447],{},[996,71446,7624],{},": A PHP parser specifically designed for handling DDEX XML files, enabling parsing and output in JSON format.",[961,71449,71450,71452],{},[996,71451,71184],{},": PHP",[961,71454,71455,71457],{},[996,71456,71190],{},": Nikhil Patil",[842,71459,71460],{},[846,71461,71464],{"href":71462,"rel":71463},"https://github.com/Alveum/DDEX",[850],[996,71465,9763],{},[958,71467,71468,71472,71477,71481],{},[961,71469,71470,71238],{},[996,71471,9870],{},[961,71473,71474,71476],{},[996,71475,7624],{},": A library for retrieving DDEX party details, which helps in managing metadata related to parties in the music supply chain.",[961,71478,71479,71452],{},[996,71480,71184],{},[961,71482,71483,71485],{},[996,71484,71190],{},": Alveum",[842,71487,71488],{},[846,71489,71492],{"href":71490,"rel":71491},"https://github.com/bnathyuw/RubyDdexParserSpike",[850],[996,71493,71494],{},"RubyDDEXParserSpike",[958,71496,71497,71501,71506,71510],{},[961,71498,71499,71238],{},[996,71500,9870],{},[961,71502,71503,71505],{},[996,71504,7624],{},": A Ruby-based tool for reading DDEX deal information using XPath to extract data from the DDEX XML.",[961,71507,71508,71217],{},[996,71509,71184],{},[961,71511,71512,71514],{},[996,71513,71190],{},": Matthew Butt",[842,71516,71517],{},[846,71518,71521],{"href":71519,"rel":71520},"https://github.com/AidanTwomey/DdexToJson",[850],[996,71522,71523],{},"DDEX to JSON",[958,71525,71526,71530,71535,71540],{},[961,71527,71528,71238],{},[996,71529,9870],{},[961,71531,71532,71534],{},[996,71533,7624],{},": A Lambda function for converting DDEX XML files into JSON format for easier integration into web services and modern data workflows.",[961,71536,71537,71539],{},[996,71538,71184],{},": Lambda",[961,71541,71542,71544],{},[996,71543,71190],{},": Aidan Twomey",[842,71546,71547],{},[846,71548,71551],{"href":71549,"rel":71550},"https://github.com/ddexnet/ern-validator-client",[850],[996,71552,71553],{},"DDEX Validator (Client)",[958,71555,71556,71560,71565,71569],{},[961,71557,71558,71238],{},[996,71559,9870],{},[961,71561,71562,71564],{},[996,71563,7624],{},": A JavaScript client-side validator designed for verifying ERN messages for compliance with DDEX standards.",[961,71566,71567,71366],{},[996,71568,71184],{},[961,71570,71571,71573],{},[996,71572,71190],{},": Rob deWilder",[842,71575,71576],{},[846,71577,71580],{"href":71578,"rel":71579},"https://github.com/ddexnet/ern-validator-api",[850],[996,71581,71582],{},"Client Validator (API)",[958,71584,71585,71589,71594,71598],{},[961,71586,71587,71238],{},[996,71588,9870],{},[961,71590,71591,71593],{},[996,71592,7624],{},": A validator API for ensuring ERN XML files adhere to DDEX standards.",[961,71595,71596,71366],{},[996,71597,71184],{},[961,71599,71600,71573],{},[996,71601,71190],{},[842,71603,71604],{},[846,71605,71608],{"href":71606,"rel":71607},"https://github.com/elibeta22/ddex-mesage-validator-api",[850],[996,71609,71610],{},"DDEX XML Validator",[958,71612,71613,71617,71622,71627],{},[961,71614,71615,71238],{},[996,71616,9870],{},[961,71618,71619,71621],{},[996,71620,7624],{},": An API tool to validate XML documents against XSD and Schematron schemas for DDEX ERN messages.",[961,71623,71624,71626],{},[996,71625,71184],{},": XSLT",[961,71628,71629,71631],{},[996,71630,71190],{},": elibeta22",[842,71633,71634],{},[846,71635,71638],{"href":71636,"rel":71637},"https://github.com/googleinterns/ddex-mead-parser",[850],[996,71639,71640],{},"DDEX XML/XSD to Protocol Buffer",[958,71642,71643,71648,71653,71658],{},[961,71644,71645,71647],{},[996,71646,9870],{},": MEAD (Metadata Exchange)",[961,71649,71650,71652],{},[996,71651,7624],{},": Converts DDEX XML/XSD messages into Protocol Buffer format for easier usage in applications that rely on binary data protocols.",[961,71654,71655,71657],{},[996,71656,71184],{},": Java",[961,71659,71660,71191],{},[996,71661,71190],{},[842,71663,71664],{},[846,71665,71668],{"href":71666,"rel":71667},"https://github.com/monstercat/transport-ddex",[850],[996,71669,71670],{},"Transport DDEX",[958,71672,71673,71678,71683,71687],{},[961,71674,71675,71677],{},[996,71676,9870],{},": ERN Choreography",[961,71679,71680,71682],{},[996,71681,7624],{},": A Python tool for assisting in the secure transfer of DDEX files over SFTP, enabling reliable file exchanges.",[961,71684,71685,71185],{},[996,71686,71184],{},[961,71688,71689,71691],{},[996,71690,71190],{},": Monster Cat",[842,71693,71694],{},[846,71695,71698],{"href":71696,"rel":71697},"https://github.com/moodleexpert/DDEXPythonParser",[850],[996,71699,71700],{},"DDEX Python Parser",[958,71702,71703,71707,71712,71716],{},[961,71704,71705,71238],{},[996,71706,9870],{},[961,71708,71709,71711],{},[996,71710,7624],{},": Parses DDEX XML files and converts them to JSON format for easier handling in web services and other applications.",[961,71713,71714,71185],{},[996,71715,71184],{},[961,71717,71718,71720],{},[996,71719,71190],{},": Arjun Nikhil",[842,71722,71723],{},[846,71724,71727],{"href":71725,"rel":71726},"https://github.com/miqwit/dedex",[850],[996,71728,71729],{},"DeDEX",[958,71731,71732,71736,71741,71745],{},[961,71733,71734,71238],{},[996,71735,9870],{},[961,71737,71738,71740],{},[996,71739,7624],{},": A parser for DDEX XML files specifically supporting versions 3.8.2 and 4.1 of the ERN standard.",[961,71742,71743,71452],{},[996,71744,71184],{},[961,71746,71747,71749],{},[996,71748,71190],{},": miqwit",[863,71751,71753],{"id":71752},"code-quality","Code Quality",[842,71755,71756,71757,1589,71760,71763,71764,1589,71768,71771],{},"Most of the repositories, such as ",[846,71758,71167],{"href":71163,"rel":71759},[850],[846,71761,71262],{"href":71258,"rel":71762},[850],", show clean and modular code structures that follow standard practices in their respective languages. This ensures better readability and easier debugging or extension by other developers. Several projects, especially those from large contributors like ",[846,71765,71767],{"href":71636,"rel":71766},[850],"Google's DDEX XML/XSD to Protocol Buffer",[846,71769,71350],{"href":71346,"rel":71770},[850],", adopt coding best practices such as proper documentation within the code, and meaningful commit messages. These repositories are structured in a way that supports easy adoption and scalability.",[863,71773,71775],{"id":71774},"documentation","Documentation",[842,71777,71778,71779,71782,71783,71786,71787,71790],{},"The repositories generally provide adequate README files explaining usage, installation, and setup. For example, the ",[846,71780,71200],{"href":71196,"rel":71781},[850]," repository includes clear links to additional documentation and explains the Ruby serialization process. Similarly, ",[846,71784,71262],{"href":71258,"rel":71785},[850]," includes detailed instructions for integration with Microsoft Visual Studio. While most repositories have decent documentation, some could benefit from more extensive guides. For instance, repositories like ",[846,71788,71729],{"href":71725,"rel":71789},[850]," lack comprehensive usage examples or extended documentation, which might make it harder for newcomers to implement the tool.",[863,71792,71794],{"id":71793},"community-engagement","Community Engagement",[842,71796,71797,71798,71801,71802,71805,71806,1589,71809,71812],{},"Several of the repositories are personal or small-team projects, such as ",[846,71799,71436],{"href":71432,"rel":71800},[850],", which shows limited engagement from the wider community. However, repositories like ",[846,71803,71167],{"href":71163,"rel":71804},[850]," have contributions from large organizations (Google) and are more likely to see community engagement due to their relevance and backing. Repositories like ",[846,71807,71231],{"href":71227,"rel":71808},[850],[846,71810,71292],{"href":71288,"rel":71811},[850]," have open issues, showing active participation in maintaining and improving the code. Repositories without active issue management might face difficulties in sustaining quality as the technology evolves.",[863,71814,71816],{"id":71815},"activity-maintenance","Activity & Maintenance",[842,71818,71819,71820,71823,71824,28366,71827,71830,71831,1589,71834,71838],{},"Some repositories, such as ",[846,71821,71523],{"href":71519,"rel":71822},[850],", have not seen recent updates, which raises concerns about their long-term maintainability. In contrast, repositories like ",[846,71825,71167],{"href":71163,"rel":71826},[850],[846,71828,71262],{"href":71258,"rel":71829},[850]," show regular commits and improvements, signaling that they are actively maintained. Projects like ",[846,71832,71767],{"href":71636,"rel":71833},[850],[846,71835,71837],{"href":71317,"rel":71836},[850],"DDEXUI"," use proper versioning and commit practices, ensuring clear tracking of changes and fixes over time.",[863,71840,71842],{"id":71841},"testing-cicd","Testing & CI/CD",[842,71844,71845,71846,71849,71850,71853,71854,71857],{},"Repositories with automated tests and CI/CD integration are generally more reliable in production environments. Tools like ",[846,71847,71262],{"href":71258,"rel":71848},[850]," integrate well into Microsoft Visual Studio, implying a strong emphasis on testing and validation. However, repositories such as ",[846,71851,71436],{"href":71432,"rel":71852},[850]," might lack the same level of CI/CD infrastructure. Repositories like ",[846,71855,71292],{"href":71288,"rel":71856},[850]," show robust error handling mechanisms, which improve the reliability of these tools in production.",[863,71859,71861],{"id":71860},"overall-quality-assessment","Overall Quality Assessment",[958,71863,71864,71877,71890],{},[961,71865,71866,71869,71870,1589,71873,71876],{},[996,71867,71868],{},"High Quality",": Projects like ",[846,71871,71167],{"href":71163,"rel":71872},[850],[846,71874,71262],{"href":71258,"rel":71875},[850]," show high quality in terms of code structure, documentation, and community support.",[961,71878,71879,71882,71883,1589,71886,71889],{},[996,71880,71881],{},"Moderate Quality",": Projects such as ",[846,71884,71436],{"href":71432,"rel":71885},[850],[846,71887,71729],{"href":71725,"rel":71888},[850]," offer good functionality but may require additional documentation and updates to maintain relevance.",[961,71891,71892,71895,71896,71899],{},[996,71893,71894],{},"Low Activity or Maintenance Concerns",": Some repositories, like ",[846,71897,71523],{"href":71519,"rel":71898},[850],", show signs of being outdated or have low maintenance activity, which could make them less reliable in the long term.",[842,71901,71902],{},"By analyzing these repositories across various metrics, we can conclude that while most tools are functional and well-built, their long-term value largely depends on regular updates and community engagement.",[863,71904,71906],{"id":71905},"contributing-to-the-musictech-opensource","Contributing to the MusicTech OpenSource",[842,71908,71909],{},[1027,71910],{"alt":71911,"src":71912},"DDEX Validator screenshot","/images/cdn-migrated/ddex-screenshot.webp",[842,71914,71915,71916,71921,71922,71924,71925,71928],{},"In addition to the tools offered by DDEX, the ",[846,71917,71919],{"href":18714,"rel":71918},[850],[996,71920,18716],{}," is a new open-source project developed to validate DDEX messages. Hosted by ",[996,71923,12840],{},", this tool is designed to help users ensure compliance with DDEX standards, specifically for validating ",[996,71926,71927],{},"Electronic Release Notification (ERN)"," messages.",[842,71930,71931,71932,71935,71936,1133],{},"The project can be accessed both as a ",[996,71933,71934],{},"web-based tool"," and through its ",[996,71937,71938],{},"GitHub repository",[958,71940,71941,71951,71961],{},[961,71942,71943,71946,71947,71950],{},[996,71944,71945],{},"Web Tool",": The ",[846,71948,18716],{"href":18714,"rel":71949},[850]," allows users to upload DDEX XML files and check their conformance with the official standards. This provides a quick and user-friendly way for developers and rights holders to verify their metadata.",[961,71952,71953,27851,71955,71960],{},[996,71954,15320],{},[846,71956,71959],{"href":71957,"rel":71958},"https://github.com/musictechlab/ddex-validator",[850],"MTL DDEX Validator on GitHub"," offers the full codebase, allowing developers to contribute, clone, or modify the validator to suit their needs. This Python-based project focuses on ensuring accurate and up-to-date validations of XML files used for music metadata exchange, making it an invaluable tool for digital service providers and record labels.",[961,71962,71963,71185],{},[996,71964,71184],{},[863,71966,71968],{"id":71967},"additional-resource-musictech-open-source-catalog","Additional Resource: MusicTech Open-Source Catalog",[842,71970,5119,71971,71977],{},[846,71972,71974],{"href":51801,"rel":71973},[850],[996,71975,71976],{},"MusicTech Open-Source Catalog"," by MusicTech Lab is a dedicated resource for music technology tools, including DDEX-specific projects. It offers a variety of open-source tools designed to help developers in the music industry.",[863,71979,71981],{"id":71980},"in-need-of-ddex-standard-implementation","In need of DDEX standard implementation?",[842,71983,71984,71985,71987,71988,71994],{},"If you need expert support for integrating DDEX standards into your digital music platform, ",[996,71986,12840],{}," offers professional services to streamline implementation. Learn more and get started with DDEX integration by visiting the ",[846,71989,71991],{"href":50251,"rel":71990},[850],[996,71992,71993],{},"MusicTech Lab DDEX Integration Services"," for personalized solutions tailored to your business needs.",{"title":728,"searchDepth":729,"depth":729,"links":71996},[71997,71998,71999,72000,72001,72002,72003,72004,72005,72006,72007,72008],{"id":9850,"depth":729,"text":9851},{"id":71142,"depth":729,"text":71143},{"id":71154,"depth":729,"text":71155},{"id":71752,"depth":729,"text":71753},{"id":71774,"depth":729,"text":71775},{"id":71793,"depth":729,"text":71794},{"id":71815,"depth":729,"text":71816},{"id":71841,"depth":729,"text":71842},{"id":71860,"depth":729,"text":71861},{"id":71905,"depth":729,"text":71906},{"id":71967,"depth":729,"text":71968},{"id":71980,"depth":729,"text":71981},"2024-09-12T00:00:00.000Z","Explore DDEX open-source projects that simplify music metadata management, enhance rights tracking, and streamline royalty distribution.",{"src":72012},"/images/blog/musictechlab_blog_ddex-open-source-projects-review.webp",{"enabled":738,"items":72014},[72015,72017,72019,72021],{"text":72016,"icon":5365},"20+ open source DDEX tools exist across Python, Ruby, C#, PHP, JavaScript, and Java.",{"text":72018,"icon":52092},"Google's dsrf parser and the DDEX Validator are the highest-quality maintained projects.",{"text":72020,"icon":3847},"Many repositories lack recent updates, raising long-term maintainability concerns.",{"text":72022,"icon":31803},"MTL DDEX Validator is an open source web tool for validating ERN messages.",{},{"title":132,"description":72010},[9763,5523,3857],"bMxlBwMk4hn1qU8ZQaxlPANspBiCrHFJawRIHsdTwkk",{"id":72028,"title":18,"authors":723,"badge":72029,"body":72030,"category":4990,"client":72444,"date":72447,"description":72448,"extension":734,"faq":723,"featured":738,"featuredOrder":3467,"hidden":69,"image":72449,"keyTakeaways":72451,"meta":72461,"navigation":738,"path":19,"seo":72462,"status":723,"stem":20,"tags":72463,"teaser":723,"__hash__":72464},"posts/blog/case-study/dlm-music-catalog-case-study.md",{"label":5,"color":50099},{"type":725,"value":72031,"toc":72429},[72032,72035,72037,72072,72074,72146,72148,72166,72168,72181,72185,72194,72198,72201,72204,72221,72224,72227,72230,72234,72237,72239,72265,72269,72314,72319,72321,72326,72332,72339,72343,72346,72360,72363,72387,72390,72403,72408,72410,72412],[842,72033,72034],{},"The client's music catalogue offers direct music licenses protected from copyright claims. Content creators and professional storytellers can use their content without worrying about legalities. Platform's license covers worldwide copyright, meaning users can play their song anywhere and everywhere. Clients' platform provides soundtracks for movies, social media, trailers, TV shows, commercials, public spaces, podcasts and more.",[863,72036,52003],{"id":52002},[1045,72038,72040,72044,72047,72051,72054,72057,72061,72064,72067],{"className":72039},[1048,1049,1050,1051,1052],[1054,72041],{"description":72042,"title":72043,"icon":7546},"Back-office catalogue management","Backend Dashboard",[1054,72045],{"description":72046,"title":52010,"icon":52124},"Interface for web & mobile app",[1054,72048],{"description":72049,"title":72050,"icon":1067},"Responsive Web Design","Frontend App",[1054,72052],{"description":72053,"title":52027,"icon":3850},"Content management system-based",[1054,72055],{"description":72056,"title":65688,"icon":4855},"Designed for iOS/Android",[1054,72058],{"description":72059,"title":72060,"icon":9547},"Customisable Javascript audio player","Web Music Player",[1054,72062],{"description":72063,"title":52120,"icon":52018},"Subscriptions engine",[1054,72065],{"description":65410,"title":72066,"icon":2917},"Amazon AWS",[1054,72068],{"description":72069,"title":72070,"icon":72071},"Single-track license purchases","E-Commerce","i-lucide-shopping-cart",[863,72073,65720],{"id":65719},[958,72075,72076,72082,72088,72093,72099,72105,72110,72116,72122,72128,72134,72140],{},[961,72077,72078,72081],{},[996,72079,72080],{},"Music Catalog"," - the list of tracks combined with categories",[961,72083,72084,72087],{},[996,72085,72086],{},"Login / Register"," - Authorisation & authentication module (with Social Auth)",[961,72089,72090],{},[996,72091,72092],{},"Reset Password",[961,72094,72095,72098],{},[996,72096,72097],{},"My Account"," - User's page and account settings (downloads, likes, account deletion)",[961,72100,72101,72104],{},[996,72102,72103],{},"Subscriptions"," - Easy, manageable subscription plans",[961,72106,72107,72109],{},[996,72108,37230],{}," - with advanced filters by categories, genre, mood, BPM, and more",[961,72111,72112,72115],{},[996,72113,72114],{},"Playlist"," - created by staff members or users themselves",[961,72117,72118,72121],{},[996,72119,72120],{},"Liked songs"," - favourites module",[961,72123,72124,72127],{},[996,72125,72126],{},"Downloads"," - possibility to download tracks to the device",[961,72129,72130,72133],{},[996,72131,72132],{},"FAQ"," - Manageable frequently asked questions",[961,72135,72136,72139],{},[996,72137,72138],{},"Contact Forms"," - Different Forms for personal or company use",[961,72141,72142,72145],{},[996,72143,72144],{},"CMS Pages"," - Pages such as policy, about, and more",[863,72147,65386],{"id":65385},[1045,72149,72151,72155,72159,72163],{"className":72150},[1048,1049,1765,1051,1052],[1054,72152],{"description":72153,"title":72154},"Backend development","Python 3",[1054,72156],{"description":72157,"title":72158},"GraphQL API layer","Hasura",[1054,72160],{"description":72161,"title":72162},"Frontend framework","Vue.js",[1054,72164],{"description":72165,"title":47091},"Mobile app development",[863,72167,66002],{"id":66001},[958,72169,72170,72173,72176,72179],{},[961,72171,72172],{},"Product management",[961,72174,72175],{},"Product design",[961,72177,72178],{},"Frontend & Backend",[961,72180,52014],{},[863,72182,72184],{"id":72183},"the-music-library-from-scratch","The Music Library From Scratch",[842,72186,72187,72188,72193],{},"The client's primary purpose in building the platform was to start selling licences online. The client had a well-prepared features list. Nevertheless, starting this project without splitting it into smaller parts was challenging. After the first discovery call, we knew the budget. The scope was still far from defined, but it was good enough to start implementing the very first components of the platform - for instance, Web Audio Player (",[846,72189,72192],{"href":72190,"rel":72191},"https://www.musictechlab.io/blog/why-we-decided-to-use-wavesurfer",[850],"read more","). The project began in May 2022 and finished in September 2023.",[863,72195,72197],{"id":72196},"challenge-poc-mvp","Challenge: POC & MVP",[842,72199,72200],{},"One of the biggest challenges was to organise the project and split it into smaller parts. After a few workshops, we figured out how and where to start.",[842,72202,72203],{},"Some of the questions raised during the workshops:",[991,72205,72206,72209,72212,72215,72218],{},[961,72207,72208],{},"Can we estimate such a big project based on files provided by the client?",[961,72210,72211],{},"Is the known budget enough to cover expenses for the first three months of development?",[961,72213,72214],{},"How fast we can set up the team?",[961,72216,72217],{},"Where do you start with these big topics in the scope?",[961,72219,72220],{},"How to design a system to provide value for end-users?",[842,72222,72223],{},"These (and many more) questions headed us to the next steps, where we prepared a milestones plan like the below:",[3572,72225],{":items":72226},"[{\"title\":\"Milestone 0: Discovery\",\"description\":\"Discovery Call — listen to client needs. Discovery Analysis — collect info from stakeholders. Estimate — business decisions & cooperation start.\",\"icon\":\"i-lucide-search\"},{\"title\":\"Milestone 1: Proof of Concept\",\"description\":\"UI — polish designs provided by the client. DB API — design & implement database structure. Web Audio Player — PoC for audio player library. CMS — integrate external CMS API. Web App — frontend framework & core feature mocks.\",\"icon\":\"i-lucide-flask-conical\"},{\"title\":\"Milestone 2: MVP Building\",\"description\":\"Backend API — GraphQL-based API. Subscriptions — Stripe engine integration. UX — improve user experience. Frontend Platform — auth, search, my account & more. Imports — client's database tracks imports.\",\"icon\":\"i-lucide-hammer\"},{\"title\":\"Milestone 3: Mobile App\",\"description\":\"UI/UX — new wireframes for mobile. iOS/Android — prepare infrastructure. Development — Flutter framework.\",\"icon\":\"i-lucide-smartphone\"},{\"title\":\"Milestone 4: Pre Launch\",\"description\":\"UI/UX — redesign with world-class designer. iScala — Stripe Invoicing integration. AWS — dev, staging & production environments. RWD polishing.\",\"icon\":\"i-lucide-rocket\"},{\"title\":\"Milestone 5: MVP Launch\",\"description\":\"Core feature improvements. Testing & bug fixing. Go live.\",\"icon\":\"i-lucide-check-circle\"},{\"title\":\"Milestone 6: Handover\",\"description\":\"Finalise documentation & transfer knowledge. Training with client's customer service. Move project to client's new team and infrastructure.\",\"icon\":\"i-lucide-handshake\"}]",[842,72228,72229],{},"Even though the client provided us with UI/UX designs, we had to solve many complex problems. UI/UX designs covered only half of the functionalities, and we had to precisely define user scenarios from dozens of cases. The second issue was that the client's UI/UX designer didn't cooperate with developers to validate if everything he designed was designed in a doable way. The third thing we could improve with the client was to publish the first results earlier and then improve the system with new deliverables. Thanks to this approach, we could have avoided unnecessary coding. Eventually, we implemented all the functions we assumed and added new features, e.g. additional validations and integrations with 3rd party providers.",[863,72231,72233],{"id":72232},"solution-deliverables","Solution: Deliverables",[842,72235,72236],{},"Our solution was to prepare the central API so that consumers, like mobile and web apps, could read/write data. The main idea was to keep the front end and back end separated. Because of the limited budget, we decided to start with a monolith backend.",[1074,72238,8484],{"id":8483},[1045,72240,72242,72245,72249,72252,72255,72259,72262],{"className":72241},[1048,1049,1050,50642,1051,1052],[1054,72243],{"description":72244,"title":65392,"icon":6395},"Python, Django",[1054,72246],{"description":72247,"title":26278,"icon":72248},"TypeScript, Vue.js","i-lucide-monitor",[1054,72250],{"description":72251,"title":52027,"icon":3850},"Sanity.io",[1054,72253],{"description":72254,"title":11843,"icon":52124},"Hasura, GraphQL",[1054,72256],{"description":72257,"title":72258,"icon":9547},"JavaScript, Vue.js","Web Player",[1054,72260],{"description":72261,"title":65410,"icon":2917},"Amazon AWS, Load Balancer",[1054,72263],{"description":72264,"title":52014,"icon":4855},"Flutter framework",[1074,72266,72268],{"id":72267},"tools-services","Tools & Services",[1045,72270,72272,72277,72280,72285,72288,72292,72295,72300,72302,72306,72310],{"className":72271},[1048,50574,1050,50642,50239,1052],[1054,72273],{"description":72274,"title":72275,"icon":72276},"Project management","Jira / Trello","i-lucide-kanban",[1054,72278],{"description":71775,"title":72279,"icon":8790},"Notion",[1054,72281],{"description":72282,"title":72283,"icon":72284},"Communication","Slack","i-lucide-message-circle",[1054,72286],{"description":72287,"title":15320,"icon":1779},"Source code",[1054,72289],{"description":72290,"title":72291,"icon":1067},"Frontend builds","Netlify",[1054,72293],{"description":72294,"title":52120,"icon":52018},"Payments & subscriptions",[1054,72296],{"description":72297,"title":72298,"icon":72299},"Cross-browser testing","BrowserStack","i-lucide-monitor-check",[1054,72301],{"description":65815,"title":65816,"icon":64001},[1054,72303],{"description":72304,"title":72305,"icon":3649},"Health checks","UptimeRobot",[1054,72307],{"description":72308,"title":72309,"icon":66072},"iOS/Android builds","Codemagic",[1054,72311],{"description":72312,"title":72313,"icon":7498},"Password management","Bitwarden",[1572,72315,72316],{},[842,72317,72318],{},"From the data schema perspective, the project was not complicated. The biggest challenge was custom, dedicated business logic — some features were disabled for selected countries, and some were limited in the mobile app.",[863,72320,66034],{"id":66033},[41054,72322,72323],{},[842,72324,72325],{},"MusicTech Lab was thoroughly engaged throughout the partnership, successfully delivering a fully-functioning MVP. The team's workflow was highly organized, and internal stakeholders were particularly impressed with the vendor's vast technical expertise and solutions.",[842,72327,72328,72331],{},[996,72329,72330],{},"Paweł Przetacznik"," — CIO, IMS S.A",[842,72333,72334],{},[846,72335,72338],{"href":72336,"rel":72337},"https://clutch.co/profile/musictech-lab#review-2063393",[850],"See full review on Clutch",[863,72340,72342],{"id":72341},"summary-music-web","Summary: Music & Web",[842,72344,72345],{},"Big projects come from big ideas. We were happy to work on this project from the first day. The project was a real challenge for us and the client. The value provided for the client's end-users - with high-quality content - was also essential. After many months of development, the project is live. End-users can buy licenses, browse catalogues and subscribe to monthly/yearly plans. The project had two significant parts:",[958,72347,72348,72354],{},[961,72349,72350,72353],{},[996,72351,72352],{},"PoC & MVP"," - Fixed budget and defined scope for the first five months",[961,72355,72356,72359],{},[996,72357,72358],{},"Post-MVP"," - Time and material for implementing, polishing and delivering features for the next seven months",[842,72361,72362],{},"During many months of development, the project engaged dozens of stakeholders. Around twenty-five people were involved. The core team consisted:",[958,72364,72365,72368,72370,72373,72375,72378,72381,72384],{},[961,72366,72367],{},"Tech Lead & DevOps Expert",[961,72369,66790],{},[961,72371,72372],{},"Regular Backend Developer",[961,72374,54026],{},[961,72376,72377],{},"Regular Frontend Developer",[961,72379,72380],{},"UI/UX Designer",[961,72382,72383],{},"Manual Tester",[961,72385,72386],{},"Business Analytics",[842,72388,72389],{},"Our already prepared products helped us to focus on developing the custom business logic:",[958,72391,72392,72397,72400],{},[961,72393,72394],{},[846,72395,72396],{"href":35},"Mobile App Skeleton for Music Catalogs",[961,72398,72399],{},"Figma White-Label Music Platform",[961,72401,72402],{},"Backend API V1 (monolith) - now being redesigned as micro-services architecture",[1032,72404,72405],{},[842,72406,72407],{},"There are no perfect projects. Our transparent approach means the client always knows what, why, when, and who. During the entire development process, we transfer all knowledge about the project to the client.",[4937,72409],{},[863,72411,51026],{"id":51025},[1045,72413,72415,72419,72422,72426],{"className":72414},[13033,50238,50239,1052],[50241,72416],{"color":50243,"label":72417,"to":72418,"variant":50246,"target":50245},"iOS App","https://apps.apple.com/us/app/closer-music/id1637404510",[50241,72420],{"color":50243,"label":49659,"to":72421,"variant":50246,"target":50245},"https://play.google.com/store/apps/details?id=com.closermusic",[50241,72423],{"color":50249,"label":72424,"to":72425,"variant":50246,"target":50245},"Web Platform","https://www.closermusic.com",[50241,72427],{"color":50249,"label":72428,"to":35,"variant":50246},"Mobile App Case Study",{"title":728,"searchDepth":729,"depth":729,"links":72430},[72431,72432,72433,72434,72435,72436,72437,72441,72442,72443],{"id":52002,"depth":729,"text":52003},{"id":65719,"depth":729,"text":65720},{"id":65385,"depth":729,"text":65386},{"id":66001,"depth":729,"text":66002},{"id":72183,"depth":729,"text":72184},{"id":72196,"depth":729,"text":72197},{"id":72232,"depth":729,"text":72233,"children":72438},[72439,72440],{"id":8483,"depth":1112,"text":8484},{"id":72267,"depth":1112,"text":72268},{"id":66033,"depth":729,"text":66034},{"id":72341,"depth":729,"text":72342},{"id":51025,"depth":729,"text":51026},{"name":72445,"logo":72446},"Closer Music","/images/logos/closermusic.webp","2024-09-01T00:00:00.000Z","How we built a music licensing platform with thousands of songs across web, iOS, and Android, featuring subscriptions and a custom audio player.",{"src":72450,"hasLogo":738},"/images/case-studies/Closer_Music_MusicTech_Lab_Case_Study.webp",{"enabled":738,"items":72452},[72453,72455,72457,72459],{"text":72454,"icon":9547},"Full music licensing platform delivered in 12 months across web, iOS, and Android.",{"text":72456,"icon":4845},"Project split into 7 milestones, from discovery to handover, with a 25-person team.",{"text":72458,"icon":5365},"Custom audio player, Stripe subscriptions, and Hasura GraphQL API built from scratch.",{"text":72460,"icon":3271},"PoC and MVP completed within 5 months on a fixed budget.",{},{"title":18,"description":72448},[4990,731],"rJotAR7S9FYQoM-c96S4UJjfWkoL0C8QmLuFwtrpa3k",{"id":72466,"title":217,"authors":723,"badge":723,"body":72467,"category":5678,"client":723,"date":73547,"description":73548,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":73549,"keyTakeaways":723,"meta":73551,"navigation":738,"path":218,"seo":73552,"status":723,"stem":219,"tags":73553,"teaser":723,"__hash__":73554},"posts/blog/newsletter/music-industry-tech-openings-august-2024-update.md",{"type":725,"value":72468,"toc":73545},[72469,72479,72486,72496,72500,72510,72539,72549,72562,72573,72590,72600,72612,72622,72636,72646,72654,72665,72678,72688,72692,72702,72716,72726,72732,72743,72762,72772,72776,72786,72794,72804,72809,72819,72825,72835,72859,72869,72883,72894,72922,72936,72940,72950,72956,72966,72970,72980,73005,73015,73025,73035,73046,73057,73078,73086,73092,73103,73115,73120,73130,73136,73147,73157,73169,73198,73212,73216,73227,73241,73252,73263,73275,73307,73318,73322,73333,73351,73361,73365,73376,73380,73392,73400,73410,73422,73433,73446,73460,73468,73478,73493,73503,73529,73539,73543],[842,72470,72471,72473,72476,59259],{},[996,72472,55602],{},[846,72474,69867],{"href":69867,"rel":72475},[850],[846,72477,69871],{"href":69871,"rel":72478},[850],[958,72480,72481,72483],{},[961,72482,69880],{},[961,72484,72485],{},"Senior Data Engineer (analytics)",[842,72487,72488,72490,72493,59259],{},[996,72489,69886],{},[846,72491,69891],{"href":69891,"rel":72492},[850],[846,72494,69895],{"href":69895,"rel":72495},[850],[958,72497,72498],{},[961,72499,52782],{},[842,72501,72502,72504,72507,69914],{},[996,72503,5059],{},[846,72505,66155],{"href":66155,"rel":72506},[850],[846,72508,69912],{"href":69912,"rel":72509},[850],[958,72511,72512,72515,72518,72521,72524,72527,72530,72532,72535,72537],{},[961,72513,72514],{},"SDE-II, Amazon Music- Turbo",[961,72516,72517],{},"Software Development Engineer, Amazon Music",[961,72519,72520],{},"Applied Scientist - Amazon Music - Search Relevance",[961,72522,72523],{},"Applied Scientist, Amazon Music Machine Learning/Personalization",[961,72525,72526],{},"Sr. Software Development Engineer (Ruby) - ART19, ART19",[961,72528,72529],{},"Sr Software Dev Manager, Amazon Digital Acceleration",[961,72531,69931],{},[961,72533,72534],{},"Software Dev Engineer - Test II, Music Growth",[961,72536,69937],{},[961,72538,70008],{},[842,72540,72541,72543,72546,69950],{},[996,72542,55633],{},[846,72544,55638],{"href":55638,"rel":72545},[850],[846,72547,55642],{"href":55642,"rel":72548},[850],[958,72550,72551,72553,72555,72557,72560],{},[961,72552,55651],{},[961,72554,57967],{},[961,72556,55656],{},[961,72558,72559],{},"Senior Information Security (InfoSec) Engineer",[961,72561,55664],{},[842,72563,72564,72566,72569,72572],{},[996,72565,56866],{},[846,72567,56871],{"href":56871,"rel":72568},[850],[846,72570,69975],{"href":69975,"rel":72571},[850],"\nLocation(s): 🇺🇸 🇮🇪 🇩🇪 🇨🇳",[958,72574,72575,72578,72581,72584,72587],{},[961,72576,72577],{},"iOS macOS Software Engineer - Audio/Music Engineering",[961,72579,72580],{},"iPhone Systems Electrical Engineer - Audio",[961,72582,72583],{},"Audio Hardware Systems Integration Engineer",[961,72585,72586],{},"Noise & Vibration Engineer | Mechanical Noise",[961,72588,72589],{},"Systems Performance Architect",[842,72591,72592,72594,72597,58797],{},[996,72593,54964],{},[846,72595,70017],{"href":70017,"rel":72596},[850],[846,72598,70021],{"href":70021,"rel":72599},[850],[958,72601,72602,72604,72606,72608,72610],{},[961,72603,52662],{},[961,72605,54982],{},[961,72607,54990],{},[961,72609,70029],{},[961,72611,58802],{},[842,72613,72614,72616,72619,59095],{},[996,72615,53942],{},[846,72617,53947],{"href":53947,"rel":72618},[850],[846,72620,53951],{"href":53951,"rel":72621},[850],[958,72623,72624,72627,72630,72633],{},[961,72625,72626],{},"Ingénieur Développement Mobile",[961,72628,72629],{},"Ingénieur Logiciel",[961,72631,72632],{},"Ingénieur Traitement du Signal",[961,72634,72635],{},"Ingénieur Développement de système temps réel Linux",[842,72637,72638,72640,72643,58412],{},[996,72639,55706],{},[846,72641,55711],{"href":55711,"rel":72642},[850],[846,72644,55715],{"href":55715,"rel":72645},[850],[958,72647,72648,72650,72652],{},[961,72649,55723],{},[961,72651,55726],{},[961,72653,70072],{},[842,72655,72656,72658,72661,72664],{},[996,72657,53446],{},[846,72659,53451],{"href":53451,"rel":72660},[850],[846,72662,53455],{"href":53455,"rel":72663},[850],"\nLocation(s): 🇦🇺 🇬🇧",[958,72666,72667,72669,72671,72674,72676],{},[961,72668,70095],{},[961,72670,52782],{},[961,72672,72673],{},"Senior Front End Engineer (Vue Js)",[961,72675,52504],{},[961,72677,54570],{},[842,72679,72680,72682,72685,70120],{},[996,72681,70109],{},[846,72683,70114],{"href":70114,"rel":72684},[850],[846,72686,70118],{"href":70118,"rel":72687},[850],[958,72689,72690],{},[961,72691,55776],{},[842,72693,72694,72696,72699,70145],{},[996,72695,70136],{},[846,72697,54503],{"href":54503,"rel":72698},[850],[846,72700,54507],{"href":54507,"rel":72701},[850],[958,72703,72704,72706,72709,72712,72714],{},[961,72705,70153],{},[961,72707,72708],{},"Senior Backend Engineer, Social team",[961,72710,72711],{},"Senior Backend Engineer, Core Team",[961,72713,58844],{},[961,72715,69200],{},[842,72717,72718,72720,72723,58412],{},[996,72719,70170],{},[846,72721,70175],{"href":70175,"rel":72722},[850],[846,72724,70179],{"href":70179,"rel":72725},[850],[958,72727,72728,72730],{},[961,72729,52376],{},[961,72731,70187],{},[842,72733,72734,72736,72739,72742],{},[996,72735,56369],{},[846,72737,56374],{"href":56374,"rel":72738},[850],[846,72740,56378],{"href":56378,"rel":72741},[850],"\nLocation(s): 🇫🇷 🇮🇳",[958,72744,72745,72747,72749,72752,72754,72757,72760],{},[961,72746,56409],{},[961,72748,52742],{},[961,72750,72751],{},"Engineering Manager – Data",[961,72753,56406],{},[961,72755,72756],{},"Business Analyst",[961,72758,72759],{},"Senior Leader – IT",[961,72761,52653],{},[842,72763,72764,72766,72769,58516],{},[996,72765,53137],{},[846,72767,53142],{"href":53142,"rel":72768},[850],[846,72770,53146],{"href":53146,"rel":72771},[850],[958,72773,72774],{},[961,72775,70242],{},[842,72777,72778,72780,72783,59059],{},[996,72779,53630],{},[846,72781,53635],{"href":53635,"rel":72782},[850],[846,72784,53639],{"href":53639,"rel":72785},[850],[958,72787,72788,72790,72792],{},[961,72789,53649],{},[961,72791,53652],{},[961,72793,53655],{},[842,72795,72796,72798,72801,58597],{},[996,72797,70266],{},[846,72799,70271],{"href":70271,"rel":72800},[850],[846,72802,70275],{"href":70275,"rel":72803},[850],[958,72805,72806],{},[961,72807,72808],{},"Technical Project Manager",[842,72810,72811,72813,72816,70294],{},[996,72812,52450],{},[846,72814,52455],{"href":52455,"rel":72815},[850],[846,72817,52459],{"href":52459,"rel":72818},[850],[958,72820,72821,72823],{},[961,72822,54717],{},[961,72824,55748],{},[842,72826,72827,72829,72832,70314],{},[996,72828,66242],{},[846,72830,57256],{"href":57256,"rel":72831},[850],[846,72833,70312],{"href":70312,"rel":72834},[850],[958,72836,72837,72839,72841,72843,72845,72847,72849,72851,72853,72855,72857],{},[961,72838,55084],{},[961,72840,69263],{},[961,72842,70338],{},[961,72844,70341],{},[961,72846,70344],{},[961,72848,70319],{},[961,72850,70322],{},[961,72852,70325],{},[961,72854,70328],{},[961,72856,70331],{},[961,72858,70008],{},[842,72860,72861,72863,72866,70360],{},[996,72862,55153],{},[846,72864,55158],{"href":55158,"rel":72865},[850],[846,72867,70358],{"href":70358,"rel":72868},[850],[958,72870,72871,72873,72876,72878,72881],{},[961,72872,58205],{},[961,72874,72875],{},"Full-Stack Engineer (Backend leaning)",[961,72877,69172],{},[961,72879,72880],{},"iOS Developer",[961,72882,69266],{},[842,72884,72885,72887,72890,72893],{},[996,72886,52356],{},[846,72888,52361],{"href":52361,"rel":72889},[850],[846,72891,55959],{"href":55959,"rel":72892},[850],"\nLocation(s): 🇲🇽 🇺🇸",[958,72895,72896,72899,72902,72905,72908,72910,72913,72916,72919],{},[961,72897,72898],{},"Back-End developer",[961,72900,72901],{},"Full-Stack developer",[961,72903,72904],{},"IT POS Support Analyst",[961,72906,72907],{},"IT Support Technician",[961,72909,70395],{},[961,72911,72912],{},"Software Engineer II, DSP",[961,72914,72915],{},"Software Engineer II, Embedded",[961,72917,72918],{},"Sr. Electrical Engineer, Mixed Signal",[961,72920,72921],{},"Staff Electrical Engineer, Mixed Signal",[842,72923,72924,72927,72931,72935],{},[996,72925,72926],{},"Hance",[846,72928,72929],{"href":72929,"rel":72930},"https://www.linkedin.com/company/hance-ai/",[850],[846,72932,72933],{"href":72933,"rel":72934},"https://join.com/companies/hance",[850],"\nLocation(s): 🇳🇴",[958,72937,72938],{},[961,72939,52546],{},[842,72941,72942,72944,72947,58775],{},[996,72943,53663],{},[846,72945,53668],{"href":53668,"rel":72946},[850],[846,72948,70415],{"href":70415,"rel":72949},[850],[958,72951,72952,72954],{},[961,72953,70421],{},[961,72955,70424],{},[842,72957,72958,72960,72963,58690],{},[996,72959,56176],{},[846,72961,70450],{"href":70450,"rel":72962},[850],[846,72964,56185],{"href":56185,"rel":72965},[850],[958,72967,72968],{},[961,72969,53340],{},[842,72971,72972,72974,72977,70472],{},[996,72973,52701],{},[846,72975,66612],{"href":66612,"rel":72976},[850],[846,72978,70470],{"href":70470,"rel":72979},[850],[958,72981,72982,72984,72986,72988,72991,72993,72995,72997,72999,73001,73003],{},[961,72983,70501],{},[961,72985,70504],{},[961,72987,70507],{},[961,72989,72990],{},"Insomniac - Backend Software Engineer",[961,72992,55027],{},[961,72994,70483],{},[961,72996,70486],{},[961,72998,70489],{},[961,73000,70492],{},[961,73002,70495],{},[961,73004,70008],{},[842,73006,73007,73009,73012,58412],{},[996,73008,54468],{},[846,73010,54473],{"href":54473,"rel":73011},[850],[846,73013,54477],{"href":54477,"rel":73014},[850],[958,73016,73017,73019,73021,73023],{},[961,73018,70527],{},[961,73020,52504],{},[961,73022,55816],{},[961,73024,70532],{},[842,73026,73027,73029,73032,70546],{},[996,73028,57107],{},[846,73030,70541],{"href":70541,"rel":73031},[850],[846,73033,57118],{"href":57118,"rel":73034},[850],[958,73036,73037,73039,73041,73044],{},[961,73038,70558],{},[961,73040,53093],{},[961,73042,73043],{},"C++ Tech Lead",[961,73045,54985],{},[842,73047,73048,73050,73053,73056],{},[996,73049,53104],{},[846,73051,53109],{"href":53109,"rel":73052},[850],[846,73054,53113],{"href":53113,"rel":73055},[850],"\nLocation(s): 🇬🇷 🇱🇰 🇧🇬",[958,73058,73059,73061,73064,73067,73070,73073,73075],{},[961,73060,55788],{},[961,73062,73063],{},"System Integrations Analyst",[961,73065,73066],{},"Junior System Integration Analyst",[961,73068,73069],{},"Software Engineer in Test",[961,73071,73072],{},"Catalog Management – RM team, Backend Engineer",[961,73074,52653],{},[961,73076,73077],{},"Senior Frontend Engineer (React)",[842,73079,73080,73082],{},[996,73081,55430],{},[846,73083,73084],{"href":73084,"rel":73085},"https://www",[850],[842,73087,73088,73089,70632],{},".linkedin.com/company/podimo/\n",[846,73090,70630],{"href":70630,"rel":73091},[850],[958,73093,73094,73097,73100],{},[961,73095,73096],{},"Senior Growth Analyst",[961,73098,73099],{},"Frontend Engineer, Membership",[961,73101,73102],{},"DevOps Engineer, Platform",[842,73104,73105,73108,73111,58690],{},[996,73106,73107],{},"PRS for Music",[846,73109,58684],{"href":58684,"rel":73110},[850],[846,73112,73113],{"href":73113,"rel":73114},"https://careers.prsformusic.com/jobs/",[850],[958,73116,73117],{},[961,73118,73119],{},"Senior Security Operations Analyst",[842,73121,73122,73124,73127,59334],{},[996,73123,70650],{},[846,73125,70655],{"href":70655,"rel":73126},[850],[846,73128,70659],{"href":70659,"rel":73129},[850],[958,73131,73132,73134],{},[961,73133,70674],{},[961,73135,70668],{},[842,73137,73138,73140,73143,73146],{},[996,73139,56343],{},[846,73141,56348],{"href":56348,"rel":73142},[850],[846,73144,66866],{"href":66866,"rel":73145},[850],"\nLocation(s): 🇳🇿",[958,73148,73149,73152,73154],{},[961,73150,73151],{},"AI Integration Architect",[961,73153,56361],{},[961,73155,73156],{},"Senior User Experience / Product Designer",[842,73158,73159,73161,73164,73168],{},[996,73160,54413],{},[846,73162,54418],{"href":54418,"rel":73163},[850],[846,73165,73166],{"href":73166,"rel":73167},"https://careers.siriusxm.com/careers",[850],"\nLocation(s): 🇺🇸 🇵🇱 🇷🇴",[958,73170,73171,73174,73177,73180,73182,73185,73188,73190,73193,73196],{},[961,73172,73173],{},"Principal Software Engineer, Embedded C++",[961,73175,73176],{},"Staff Data Engineer, Platform Infrastructure (IaaC)",[961,73178,73179],{},"Senior NOC Analyst",[961,73181,53398],{},[961,73183,73184],{},"Software Engineering - Android",[961,73186,73187],{},"Senior Quality Engineer",[961,73189,53430],{},[961,73191,73192],{},"Director, Science & Machine Learning - Search & Voice",[961,73194,73195],{},"Senior Software Engineer (Stencil JS) - contractor",[961,73197,70008],{},[842,73199,73200,73203,73207,73211],{},[996,73201,73202],{},"SOUNDBOKS",[846,73204,73205],{"href":73205,"rel":73206},"https://www.linkedin.com/company/soundboks/",[850],[846,73208,73209],{"href":73209,"rel":73210},"https://soundboks.recruitee.com/",[850],"\nLocation(s): 🇩🇰",[958,73213,73214],{},[961,73215,57689],{},[842,73217,73218,73220,73224,70707],{},[996,73219,66830],{},[846,73221,73222],{"href":73222,"rel":73223},"https://www.linkedin.com/company/soundcloud",[850],[846,73225,66839],{"href":66839,"rel":73226},[850],[958,73228,73229,73231,73233,73235,73237,73239],{},[961,73230,70721],{},[961,73232,53037],{},[961,73234,66856],{},[961,73236,66853],{},[961,73238,69438],{},[961,73240,70730],{},[842,73242,73243,73245,73248,58690],{},[996,73244,57773],{},[846,73246,57778],{"href":57778,"rel":73247},[850],[846,73249,73250],{"href":73250,"rel":73251},"https://www.speechmatics.com/company/careers",[850],[958,73253,73254,73256,73258,73260],{},[961,73255,52504],{},[961,73257,52586],{},[961,73259,52546],{},[961,73261,73262],{},"Staff Machine Learning Scientist",[842,73264,73265,73267,73270,73274],{},[996,73266,5070],{},[846,73268,66516],{"href":66516,"rel":73269},[850],[846,73271,73272],{"href":73272,"rel":73273},"https://www.lifeatspotify.com/",[850],"\nLocation(s): 🇬🇧 🇺🇸 🇸🇪 🇸🇬 🇹🇼",[958,73276,73277,73280,73283,73285,73288,73291,73294,73297,73300,73303,73305],{},[961,73278,73279],{},"Web Engineer, Podcast",[961,73281,73282],{},"Senior Machine Learning Infrastructure Engineer, Music",[961,73284,52546],{},[961,73286,73287],{},"Machine Learning Engineering Manager",[961,73289,73290],{},"Backend Engineer II, Compliance Engineering",[961,73292,73293],{},"Data Engineer, Content Understanding",[961,73295,73296],{},"Junior Android Engineer, Core Experience",[961,73298,73299],{},"Senior Fullstack Engineer, Backstage",[961,73301,73302],{},"Senior Security GRC Manager, SOX ITGC",[961,73304,69200],{},[961,73306,70008],{},[842,73308,73309,73311,73314,58533],{},[996,73310,55872],{},[846,73312,52483],{"href":52483,"rel":73313},[850],[846,73315,73316],{"href":73316,"rel":73317},"https://www.linkedin.com/company/splashmusicco/jobs/",[850],[958,73319,73320],{},[961,73321,72808],{},[842,73323,73324,73327,73330,58412],{},[996,73325,73326],{},"Sunomusic",[846,73328,57609],{"href":57609,"rel":73329},[850],[846,73331,57615],{"href":57615,"rel":73332},[850],[958,73334,73335,73337,73339,73341,73343,73345,73347,73349],{},[961,73336,55180],{},[961,73338,57654],{},[961,73340,66813],{},[961,73342,54490],{},[961,73344,57643],{},[961,73346,52626],{},[961,73348,57651],{},[961,73350,57648],{},[842,73352,73353,73355,73358,58412],{},[996,73354,56202],{},[846,73356,56207],{"href":56207,"rel":73357},[850],[846,73359,56211],{"href":56211,"rel":73360},[850],[958,73362,73363],{},[961,73364,56219],{},[842,73366,73367,73369,73372,59059],{},[996,73368,59050],{},[846,73370,59053],{"href":59053,"rel":73371},[850],[846,73373,73374],{"href":73374,"rel":73375},"https://jobs.ticketswap.com/",[850],[958,73377,73378],{},[961,73379,58205],{},[842,73381,73382,73384,73388,58412],{},[996,73383,5037],{},[846,73385,73386],{"href":73386,"rel":73387},"https://www.linkedin.com/company/tidal/",[850],[846,73389,73390],{"href":73390,"rel":73391},"https://careers.tidal.com/",[850],[958,73393,73394,73397],{},[961,73395,73396],{},"Product Designer, Commerce",[961,73398,73399],{},"Product Designer, Streaming",[842,73401,73402,73404,73407,58597],{},[996,73403,52942],{},[846,73405,52947],{"href":52947,"rel":73406},[850],[846,73408,70778],{"href":70778,"rel":73409},[850],[958,73411,73412,73414,73416,73419],{},[961,73413,70792],{},[961,73415,70789],{},[961,73417,73418],{},"Director of Product Management, Hardware",[961,73420,73421],{},"Staff Android Developer",[842,73423,73424,73426,73430,58412],{},[996,73425,54845],{},[846,73427,73428],{"href":73428,"rel":73429},"https://www.linkedin.com/company/universal-audio",[850],[846,73431,57813],{"href":57813,"rel":73432},[850],[958,73434,73435,73438,73440,73442,73444],{},[961,73436,73437],{},"Vice President of Engineering",[961,73439,57823],{},[961,73441,57829],{},[961,73443,57832],{},[961,73445,54874],{},[842,73447,73448,73451,73455,73459],{},[996,73449,73450],{},"Viberate",[846,73452,73453],{"href":73453,"rel":73454},"https://www.linkedin.com/company/viberate?originalSubdomain=uk",[850],[846,73456,73457],{"href":73457,"rel":73458},"https://www.viberate.com/si/jobs/",[850],"\nLocation(s): 🇸🇮",[958,73461,73462,73465],{},[961,73463,73464],{},"PHP/Python/GOlang Developer",[961,73466,73467],{},"Frontend (REACT) Developer",[842,73469,73470,73472,73475,58516],{},[996,73471,59138],{},[846,73473,59141],{"href":59141,"rel":73474},[850],[846,73476,59145],{"href":59145,"rel":73477},[850],[958,73479,73480,73482,73484,73487,73489,73491],{},[961,73481,70834],{},[961,73483,66787],{},[961,73485,73486],{},"Senior Android KMM Developer",[961,73488,66790],{},[961,73490,54026],{},[961,73492,54118],{},[842,73494,73495,73497,73500,70856],{},[996,73496,58011],{},[846,73498,58016],{"href":58016,"rel":73499},[850],[846,73501,70854],{"href":70854,"rel":73502},[850],[958,73504,73505,73507,73509,73511,73513,73515,73518,73520,73522,73525,73527],{},[961,73506,58034],{},[961,73508,58805],{},[961,73510,57724],{},[961,73512,55664],{},[961,73514,55659],{},[961,73516,73517],{},"Junior IT Service Desk Analyst",[961,73519,58997],{},[961,73521,52376],{},[961,73523,73524],{},"Software Quality Engineer",[961,73526,55788],{},[961,73528,70008],{},[842,73530,73531,73533,73536,58690],{},[996,73532,56304],{},[846,73534,56309],{"href":56309,"rel":73535},[850],[846,73537,70903],{"href":70903,"rel":73538},[850],[958,73540,73541],{},[961,73542,55651],{},[842,73544,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":73546},[],"2024-08-23T00:00:00.000Z","Curated list of tech job openings in the music industry for August 2024. Explore roles at music startups, labels, and streaming services.",{"src":73550},"/images/blog/musictechlab_blog_music-industry-tech-openings-august-2024-update.webp",{},{"title":217,"description":73548},[5678,5523],"0Ry7lLiv5vMt83k5Fmb45b8kZZW1GCBgs9qdkwjZ9Mo",{"id":73556,"title":233,"authors":73557,"badge":723,"body":73560,"category":5678,"client":723,"date":74332,"description":74333,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":74334,"keyTakeaways":723,"meta":74336,"navigation":738,"path":234,"seo":74337,"status":723,"stem":235,"tags":74338,"teaser":723,"__hash__":74339},"posts/blog/newsletter/music-industry-tech-openings-july-2024-update.md",[73558],{"name":50093,"to":50094,"avatar":73559},{"src":50096},{"type":725,"value":73561,"toc":74284},[73562,73566,73579,73583,73594,73598,73609,73613,73624,73628,73639,73643,73654,73658,73669,73674,73686,73690,73701,73705,73716,73720,73731,73735,73746,73750,73761,73765,73776,73780,73791,73795,73806,73810,73821,73826,73837,73842,73855,73859,73871,73875,73886,73890,73901,73907,73920,73926,73939,73943,73954,73958,73969,73973,73984,73989,74000,74004,74015,74019,74030,74034,74045,74049,74061,74065,74076,74081,74092,74096,74107,74111,74123,74127,74138,74142,74153,74159,74170,74175,74186,74191,74202,74206,74217,74222,74233,74237,74248,74254,74267,74271,74282],[1074,73563,73564],{"id":55703},[996,73565,55706],{},[842,73567,73568,73570,73574,73578],{},[996,73569,52316],{},[846,73571,73573],{"href":55711,"rel":73572},[850],"LinkedIn Profile",[846,73575,73577],{"href":55715,"rel":73576},[850],"Career Page","\nLocation(s): 🇺🇸\nInformation Security Analyst (Architect)\nSenior Salesforce Engineer",[1074,73580,73581],{"id":55599},[996,73582,55602],{},[842,73584,73585,73587,73590,73593],{},[996,73586,52316],{},[846,73588,73573],{"href":69867,"rel":73589},[850],[846,73591,73577],{"href":69871,"rel":73592},[850],"\nLocation(s): 🇬🇧 🇸🇪\nSenior Software Engineer\nSenior Frontend Engineer\nTech Team Manager (Marketplace Infrastructure)",[1074,73595,73596],{"id":66150},[996,73597,5059],{},[842,73599,73600,73602,73605,73608],{},[996,73601,52316],{},[846,73603,73573],{"href":66155,"rel":73604},[850],[846,73606,73577],{"href":69912,"rel":73607},[850],"\nLocation(s): 🇺🇸 🇮🇳 🇪🇪 🇲🇽 🇩🇪\nSenior Technical Program Manager, Music\nSr. Software Development Engineer (Ruby)\nSoftware Development Engineer\nSr Manager, Product Management Tech\nSenior Product Manager\nSoftware Development Engineer\nSenior Product Manager-Tech\nSenior Product Manager",[1074,73610,73611],{"id":55630},[996,73612,55633],{},[842,73614,73615,73617,73620,73623],{},[996,73616,52316],{},[846,73618,73573],{"href":55638,"rel":73619},[850],[846,73621,73577],{"href":55642,"rel":73622},[850],"\nLocation(s): 🇱🇧\nAndroid Engineer\niOS Engineer\nBackend Engineer - Payments and Billing Systems\nFrontend Engineer - Internal Tooling\nSenior Information Security (InfoSec) Engineer",[1074,73625,73626],{"id":54961},[996,73627,54964],{},[842,73629,73630,73632,73635,73638],{},[996,73631,52316],{},[846,73633,73573],{"href":70017,"rel":73634},[850],[846,73636,73577],{"href":70021,"rel":73637},[850],"\nLocation(s): 🇮🇱\nSenior Full Stack Developer\nData Scientist\nBack End Developer\nSenior Data Scientist\nQA Engineer\nFull Stack Team Lead",[1074,73640,73641],{"id":53939},[996,73642,53942],{},[842,73644,73645,73647,73650,73653],{},[996,73646,52316],{},[846,73648,73573],{"href":53947,"rel":73649},[850],[846,73651,73577],{"href":53951,"rel":73652},[850],"\nLocation(s): 🇫🇷\nIngénieur Développement Mobile\nIngénieur logiciel\nSenior Fullstack Developer",[1074,73655,73656],{"id":70108},[996,73657,70109],{},[842,73659,73660,73662,73665,73668],{},[996,73661,52316],{},[846,73663,73573],{"href":70114,"rel":73664},[850],[846,73666,73577],{"href":70118,"rel":73667},[850],"\nLocation(s): 🇦🇪\nEngineering Manager\nSenior Software Engineer",[1074,73670,73672],{"id":73671},"audiostack",[996,73673,58491],{},[842,73675,73676,73678,73681,73685],{},[996,73677,52316],{},[846,73679,73573],{"href":58494,"rel":73680},[850],[846,73682,73577],{"href":73683,"rel":73684},"https://apply.workable.com/audiostack/",[850],"\nLocation(s): 🇬🇧\nFrontend Developer\nProduct Designer",[1074,73687,73688],{"id":70135},[996,73689,70136],{},[842,73691,73692,73694,73697,73700],{},[996,73693,52316],{},[846,73695,73573],{"href":54503,"rel":73696},[850],[846,73698,73577],{"href":54507,"rel":73699},[850],"\nLocation(s): 🇸🇬\nAndroid Developer\nSenior Backend Engineer, Ads Team\nSenior Backend Engineer, Social team\nSenior Backend Engineer, Payments and Payouts Team\nSenior Backend Engineer, Core Team",[1074,73702,73703],{"id":52554},[996,73704,52557],{},[842,73706,73707,73709,73712,73715],{},[996,73708,52316],{},[846,73710,73573],{"href":52562,"rel":73711},[850],[846,73713,73577],{"href":52566,"rel":73714},[850],"\nLocation(s): 🇬🇧\nSenior Data Scientist\nSenior MSSQL Database + Node.js Developer",[1074,73717,73718],{"id":56366},[996,73719,56369],{},[842,73721,73722,73724,73727,73730],{},[996,73723,52316],{},[846,73725,73573],{"href":56374,"rel":73726},[850],[846,73728,73577],{"href":56378,"rel":73729},[850],"\nLocation(s): 🇫🇷 🇮🇳\nLead Tech QA platform\nContent database senior administrator\nSenior Leader - IT\nSenior Data engineer\nEngineering Manager Supply Chain\nEngineering Manager - Content\nHead of Operational Excellence\nSenior Software Engineer",[1074,73732,73733],{"id":53134},[996,73734,53137],{},[842,73736,73737,73739,73742,73745],{},[996,73738,52316],{},[846,73740,73573],{"href":53142,"rel":73741},[850],[846,73743,73577],{"href":53146,"rel":73744},[850],"\nLocation(s): 🇪🇸\nSoftware Engineer (Python)\nDevOps Engineer",[1074,73747,73748],{"id":53627},[996,73749,53630],{},[842,73751,73752,73754,73757,73760],{},[996,73753,52316],{},[846,73755,73573],{"href":53635,"rel":73756},[850],[846,73758,73577],{"href":53639,"rel":73759},[850],"\nLocation(s): 🇳🇱\nSenior Frond-End Developer\nBack-End / DevOps Engineer\nUX/UI Designer",[1074,73762,73763],{"id":52447},[996,73764,52450],{},[842,73766,73767,73769,73772,73775],{},[996,73768,52316],{},[846,73770,73573],{"href":52455,"rel":73771},[850],[846,73773,73577],{"href":52459,"rel":73774},[850],"\nLocation(s): 🇬🇧\nTechnical Support Engineer\nProduct Test Engineer",[1074,73777,73778],{"id":66241},[996,73779,66242],{},[842,73781,73782,73784,73787,73790],{},[996,73783,52316],{},[846,73785,73573],{"href":57256,"rel":73786},[850],[846,73788,73577],{"href":70312,"rel":73789},[850],"\nLocation(s): 🇨🇳 🇵🇱 🇺🇸 🇮🇳\nSenior Field Application Engineer\nSenior Software Developer\nSr Distributed Research Engineer\nHead of Data Science & Machine Learning\nSr. Semantics Computer Vision Research",[1074,73792,73793],{"id":55150},[996,73794,55153],{},[842,73796,73797,73799,73802,73805],{},[996,73798,52316],{},[846,73800,73573],{"href":55158,"rel":73801},[850],[846,73803,73577],{"href":70358,"rel":73804},[850],"\nLocation(s): 🇵🇱 🇪🇸 🇬🇧 🇮🇪 🇩🇪\nAndroid Developer\nBackend Engineer\nFull-Stack Engineer\niOS Developer\nTechnical Account Manager",[1074,73807,73808],{"id":52353},[996,73809,52356],{},[842,73811,73812,73814,73817,73820],{},[996,73813,52316],{},[846,73815,73573],{"href":52361,"rel":73816},[850],[846,73818,73577],{"href":55959,"rel":73819},[850],"\nLocation(s): 🇲🇽 🇯🇵 🇺🇸\nAnalytics Engineer\nBack-End developer\nFull-Stack developer\nIT POS Support Analyst\nIT Support Technician",[1074,73822,73824],{"id":73823},"hance",[996,73825,72926],{},[842,73827,73828,73830,73833,73836],{},[996,73829,52316],{},[846,73831,73573],{"href":72929,"rel":73832},[850],[846,73834,73577],{"href":72933,"rel":73835},[850],"\nLocation(s): 🇳🇴\nMachine Learning Engineer",[1074,73838,73840],{"id":73839},"idol",[996,73841,64901],{},[842,73843,73844,73846,73850,73854],{},[996,73845,52316],{},[846,73847,73573],{"href":73848,"rel":73849},"https://www.linkedin.com/company/idol-independent-distribution-on-line-/",[850],[846,73851,73577],{"href":73852,"rel":73853},"https://idol.io/jobs/",[850],"\nLocation(s): 🇫🇷\nIT Support Apprentice\nIT Infrastructure Engineer\nInfrastructure Engineer - Dev XP Apprentice\nExpert Backend Engineer - Conversion",[1074,73856,73857],{"id":53660},[996,73858,53663],{},[842,73860,73861,73863,73866,73870],{},[996,73862,52316],{},[846,73864,73573],{"href":53668,"rel":73865},[850],[846,73867,73577],{"href":73868,"rel":73869},"https://www.inmusicbrands.com/careers/",[850],"\nLocation(s): 🇬🇧\nBuild Engineer",[1074,73872,73873],{"id":54465},[996,73874,54468],{},[842,73876,73877,73879,73882,73885],{},[996,73878,52316],{},[846,73880,73573],{"href":54473,"rel":73881},[850],[846,73883,73577],{"href":54477,"rel":73884},[850],"\nLocation(s): 🇺🇸\nLead Data Scientist\nSenior Data Scientist\nSenior Software Engineer\nSenior UI/UX Designer\nStaff Software Engineer (Data)",[1074,73887,73888],{"id":52698},[996,73889,52701],{},[842,73891,73892,73894,73897,73900],{},[996,73893,52316],{},[846,73895,73573],{"href":66612,"rel":73896},[850],[846,73898,73577],{"href":70470,"rel":73899},[850],"\nLocation(s): 🇹🇼 🇦🇺 🇺🇸 🇬🇧 🇨🇦 🇬🇷\nSoftware Engineer\nCyber Defense Analyst\nSr. Mobile Engineer\nSenior NoSQL Database Engineer\nPlatform Engineer\nSr. Systems Engineer\nAWS Databricks Administrator\nLN Concerts, System Support Analyst\nInsomniac - Dev Ops Engineer",[1074,73902,73904],{"id":73903},"musicai",[996,73905,73906],{},"Music.AI",[842,73908,73909,73911,73915,73919],{},[996,73910,52316],{},[846,73912,73573],{"href":73913,"rel":73914},"https://www.linkedin.com/company/musicai/",[850],[846,73916,73577],{"href":73917,"rel":73918},"https://music.ai/careers/",[850],"\nLocation(s): 🇺🇸 🇧🇷\nFront End Developer\nAndroid Engineer\nSenior Front End Developer",[1074,73921,73923],{"id":73922},"musicnotes",[996,73924,73925],{},"Musicnotes",[842,73927,73928,73930,73934,73938],{},[996,73929,52316],{},[846,73931,73573],{"href":73932,"rel":73933},"https://www.linkedin.com/company/musicnotes.com/",[850],[846,73935,73577],{"href":73936,"rel":73937},"https://www.musicnotes.com/careers/",[850],"\nLocation(s): 🇺🇸\nDatabase Administrator",[1074,73940,73941],{"id":57104},[996,73942,57107],{},[842,73944,73945,73947,73950,73953],{},[996,73946,52316],{},[846,73948,73573],{"href":70541,"rel":73949},[850],[846,73951,73577],{"href":57118,"rel":73952},[850],"\nProduct Designer\nData Engineer\nFrontend Tech Lead\nC++ Tech Lead\nQA Engineer",[1074,73955,73956],{"id":53101},[996,73957,53104],{},[842,73959,73960,73962,73965,73968],{},[996,73961,52316],{},[846,73963,73573],{"href":53109,"rel":73964},[850],[846,73966,73577],{"href":53113,"rel":73967},[850],"\nLocation(s): 🇬🇷 🇧🇬\nSenior Security Engineer\nAssociate Engineering Manager\nJr Backend Engineer\nSenior Data Engineer\nSenior Frontend Engineer (React)\nOM Platform - Backend Engineer",[1074,73970,73971],{"id":55427},[996,73972,55430],{},[842,73974,73975,73977,73980,73983],{},[996,73976,52316],{},[846,73978,73573],{"href":55435,"rel":73979},[850],[846,73981,73577],{"href":70630,"rel":73982},[850],"\nLocation(s): 🇩🇰 🇪🇸 🇬🇧 🇳🇱 🇱🇹 🇩🇪\nAnalytics Engineer\nBackend Engineer, Discovery\nFrontend Engineer, Membership\nDevOps Engineer, Platform\nAndroid Engineer",[1074,73985,73987],{"id":73986},"prs-for-music",[996,73988,73107],{},[842,73990,73991,73993,73996,73999],{},[996,73992,52316],{},[846,73994,73573],{"href":58684,"rel":73995},[850],[846,73997,73577],{"href":73113,"rel":73998},[850],"\nLocation(s): 🇬🇧\nSenior Security Operations Analyst",[1074,74001,74002],{"id":55512},[996,74003,55515],{},[842,74005,74006,74008,74011,74014],{},[996,74007,52316],{},[846,74009,73573],{"href":55520,"rel":74010},[850],[846,74012,73577],{"href":55524,"rel":74013},[850],"\nLocation(s): 🇪🇸 🇷🇴\nProduct Manager\nSoftware Team Lead (.Net Core)",[1074,74016,74017],{"id":70649},[996,74018,70650],{},[842,74020,74021,74023,74026,74029],{},[996,74022,52316],{},[846,74024,73573],{"href":70655,"rel":74025},[850],[846,74027,73577],{"href":70659,"rel":74028},[850],"\nLocation(s): 🇩🇪\nSenior UX Researcher\nProduct Security Engineer\nApple macOS Network Audio Driver Engineer\nSoftware Engineer - Backend Services\nEmbedded Software Engineer\nInformation Security Manager\nDirector of Modules and Platforms\nProduct Manager Website",[1074,74031,74032],{"id":56340},[996,74033,56343],{},[842,74035,74036,74038,74041,74044],{},[996,74037,52316],{},[846,74039,73573],{"href":56348,"rel":74040},[850],[846,74042,73577],{"href":66866,"rel":74043},[850],"\nLocation(s): 🇳🇿\nAI Integration Architect",[1074,74046,74047],{"id":52886},[996,74048,52889],{},[842,74050,74051,74053,74056,74060],{},[996,74052,52316],{},[846,74054,73573],{"href":52894,"rel":74055},[850],[846,74057,73577],{"href":74058,"rel":74059},"https://www.sonymusic.com/careers/",[850],"\nLocation(s): 🇺🇸\nSenior Software Engineer",[1074,74062,74063],{"id":66829},[996,74064,66830],{},[842,74066,74067,74069,74072,74075],{},[996,74068,52316],{},[846,74070,73573],{"href":73222,"rel":74071},[850],[846,74073,73577],{"href":66839,"rel":74074},[850],"\nLocation(s): 🇩🇪 🇬🇧\nSenior Cloud Network Engineer\nSenior Backend Software Engineer, Ads Team\nSenior Media Streaming Backend Engineer\nSenior On-Premise Platform Engineer",[1074,74077,74079],{"id":74078},"soundboks",[996,74080,73202],{},[842,74082,74083,74085,74088,74091],{},[996,74084,52316],{},[846,74086,73573],{"href":73205,"rel":74087},[850],[846,74089,73577],{"href":73209,"rel":74090},[850],"\nLocation(s): 🇩🇰\nLead Mechanical Engineer",[1074,74093,74094],{"id":57770},[996,74095,57773],{},[842,74097,74098,74100,74103,74106],{},[996,74099,52316],{},[846,74101,73573],{"href":57778,"rel":74102},[850],[846,74104,73577],{"href":73250,"rel":74105},[850],"\nLocation(s): 🇬🇧\nSite Reliability Engineer\nMachine Learning Engineer\nStaff Machine Learning Scientist",[1074,74108,74109],{"id":56959},[996,74110,56962],{},[842,74112,74113,74115,74118,74122],{},[996,74114,52316],{},[846,74116,73573],{"href":56967,"rel":74117},[850],[846,74119,73577],{"href":74120,"rel":74121},"https://boards.greenhouse.io/splice",[850],"\nLocation(s): 🇺🇸\nSenior Frontend Engineer, Core Engineering\nSenior Experimentation Lead, Product",[1074,74124,74125],{"id":12966},[996,74126,5070],{},[842,74128,74129,74131,74134,74137],{},[996,74130,52316],{},[846,74132,73573],{"href":66516,"rel":74133},[850],[846,74135,73577],{"href":73272,"rel":74136},[850],"\nLocation(s): 🇬🇧 🇺🇸 🇸🇪 🇸🇬 🇹🇼\nAndroid Engineer, Advertising\nBackend Engineer II, Amplify\nSenior Partner Engineer Asia\nMachine Learning Engineer\nSenior Data Engineer\nSenior Security Engineer, Detection and Response\nSenior Machine Learning Engineer\nEmbedded C++ Full Stack Engineer, Consumer Experience\nMachine Learning Engineering Manager",[1074,74139,74140],{"id":55869},[996,74141,55872],{},[842,74143,74144,74146,74149,74152],{},[996,74145,52316],{},[846,74147,73573],{"href":52483,"rel":74148},[850],[846,74150,73577],{"href":73316,"rel":74151},[850],"\nLocation(s): 🇦🇺\nFront End Developer",[1074,74154,74156],{"id":74155},"symphonic-distribution",[996,74157,74158],{},"Symphonic Distribution",[842,74160,74161,74163,74166,74169],{},[996,74162,52316],{},[846,74164,73573],{"href":54213,"rel":74165},[850],[846,74167,73577],{"href":54217,"rel":74168},[850],"\nLocation(s): 🇨🇴 🇨🇱 🇲🇽\nSoftware Developer (PHP/Laravel)",[1074,74171,74173],{"id":74172},"tidal",[996,74174,5037],{},[842,74176,74177,74179,74182,74185],{},[996,74178,52316],{},[846,74180,73573],{"href":73386,"rel":74181},[850],[846,74183,73577],{"href":73390,"rel":74184},[850],"\nLocation(s): 🇺🇸\nProduct Designer, Commerce\nSenior Software Developer",[1074,74187,74189],{"id":74188},"ticketswap",[996,74190,59050],{},[842,74192,74193,74195,74198,74201],{},[996,74194,52316],{},[846,74196,73573],{"href":59053,"rel":74197},[850],[846,74199,73577],{"href":73374,"rel":74200},[850],"\nLocation(s): 🇳🇱\nSoftware Engineer Backend (PHP)\nEngineering Manager\nAndroid Developer",[1074,74203,74204],{"id":54842},[996,74205,54845],{},[842,74207,74208,74210,74213,74216],{},[996,74209,52316],{},[846,74211,73573],{"href":73428,"rel":74212},[850],[846,74214,73577],{"href":57813,"rel":74215},[850],"\nLocation(s): 🇺🇸\nEngineering - Software\nEngineering - Hardware\nEngineering - Mechanical",[1074,74218,74220],{"id":74219},"viberate",[996,74221,73450],{},[842,74223,74224,74226,74229,74232],{},[996,74225,52316],{},[846,74227,73573],{"href":73453,"rel":74228},[850],[846,74230,73577],{"href":73457,"rel":74231},[850],"\nLocation(s): 🇸🇮\nPHP/Python/GOlang Engineer\nFrontend (REACT) Engineer",[1074,74234,74235],{"id":66770},[996,74236,59138],{},[842,74238,74239,74241,74244,74247],{},[996,74240,52316],{},[846,74242,73573],{"href":59141,"rel":74243},[850],[846,74245,73577],{"href":59145,"rel":74246},[850],"\nLocation(s): 🇪🇸\nSenior Android KMM developer\nSenior Frontend Developer\nSenior NodeJS Backend Developer",[1074,74249,74251],{"id":74250},"vydia",[996,74252,74253],{},"Vydia",[842,74255,74256,74258,74262,74266],{},[996,74257,52316],{},[846,74259,73573],{"href":74260,"rel":74261},"https://www.linkedin.com/company/vydia-inc-/",[850],[846,74263,73577],{"href":74264,"rel":74265},"https://recruiting.paylocity.com/recruiting/jobs/",[850],"\nLocation(s): 🇺🇸\nVice President of Engineering",[1074,74268,74269],{"id":56301},[996,74270,56304],{},[842,74272,74273,74275,74278,74281],{},[996,74274,52316],{},[846,74276,73573],{"href":56309,"rel":74277},[850],[846,74279,73577],{"href":70903,"rel":74280},[850],"\nLocation(s): 🇬🇧\nSenior Embedded Software Engineer\nStaff Front End Engineer",[842,74283,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":74285},[74286,74287,74288,74289,74290,74291,74292,74293,74294,74295,74296,74297,74298,74299,74300,74301,74302,74303,74304,74305,74306,74307,74308,74309,74310,74311,74312,74313,74314,74315,74316,74317,74318,74319,74320,74321,74322,74323,74324,74325,74326,74327,74328,74329,74330,74331],{"id":55703,"depth":1112,"text":55706},{"id":55599,"depth":1112,"text":55602},{"id":66150,"depth":1112,"text":5059},{"id":55630,"depth":1112,"text":55633},{"id":54961,"depth":1112,"text":54964},{"id":53939,"depth":1112,"text":53942},{"id":70108,"depth":1112,"text":70109},{"id":73671,"depth":1112,"text":58491},{"id":70135,"depth":1112,"text":70136},{"id":52554,"depth":1112,"text":52557},{"id":56366,"depth":1112,"text":56369},{"id":53134,"depth":1112,"text":53137},{"id":53627,"depth":1112,"text":53630},{"id":52447,"depth":1112,"text":52450},{"id":66241,"depth":1112,"text":66242},{"id":55150,"depth":1112,"text":55153},{"id":52353,"depth":1112,"text":52356},{"id":73823,"depth":1112,"text":72926},{"id":73839,"depth":1112,"text":64901},{"id":53660,"depth":1112,"text":53663},{"id":54465,"depth":1112,"text":54468},{"id":52698,"depth":1112,"text":52701},{"id":73903,"depth":1112,"text":73906},{"id":73922,"depth":1112,"text":73925},{"id":57104,"depth":1112,"text":57107},{"id":53101,"depth":1112,"text":53104},{"id":55427,"depth":1112,"text":55430},{"id":73986,"depth":1112,"text":73107},{"id":55512,"depth":1112,"text":55515},{"id":70649,"depth":1112,"text":70650},{"id":56340,"depth":1112,"text":56343},{"id":52886,"depth":1112,"text":52889},{"id":66829,"depth":1112,"text":66830},{"id":74078,"depth":1112,"text":73202},{"id":57770,"depth":1112,"text":57773},{"id":56959,"depth":1112,"text":56962},{"id":12966,"depth":1112,"text":5070},{"id":55869,"depth":1112,"text":55872},{"id":74155,"depth":1112,"text":74158},{"id":74172,"depth":1112,"text":5037},{"id":74188,"depth":1112,"text":59050},{"id":54842,"depth":1112,"text":54845},{"id":74219,"depth":1112,"text":73450},{"id":66770,"depth":1112,"text":59138},{"id":74250,"depth":1112,"text":74253},{"id":56301,"depth":1112,"text":56304},"2024-07-17T00:00:00.000Z","Curated list of tech job openings in the music industry for July 2024. Explore roles at music startups, labels, streaming platforms, and more.",{"src":74335},"/images/blog/musictechlab_blog_music-industry-tech-openings-july-2024-update.webp",{},{"title":233,"description":74333},[5678,5523],"6IeZZrX1GnvohPjp65_6OuFch7NelWFW7tAtZKBHr0w",{"id":74341,"title":502,"authors":74342,"badge":723,"body":74347,"category":756,"client":723,"date":74600,"description":74601,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":74602,"keyTakeaways":74604,"meta":74612,"navigation":738,"path":503,"seo":74613,"status":723,"stem":504,"tags":74614,"teaser":723,"__hash__":74616},"posts/blog/software-development/how-to-conduct-workshops-for-creative-industry.md",[74343],{"name":74344,"avatar":74345},"Adam Golański",{"src":74346},"/images/people/adam-golanski.webp",{"type":725,"value":74348,"toc":74581},[74349,74356,74359,74362,74366,74369,74373,74376,74391,74395,74410,74414,74417,74425,74429,74432,74436,74439,74450,74454,74469,74473,74476,74491,74495,74498,74509,74513,74516,74520,74523,74526,74530,74533,74536,74539,74542,74547,74550,74555,74558,74563,74566,74569,74573,74576],[842,74350,74351,74352,74355],{},"Imagine a room filled with people, gathered around a table covered with laptops and sketches. The atmosphere is charged with anticipation and creativity as they begin a workshop to develop the ",[964,74353,74354],{},"next big thing"," in music technology. This isn’t a typical corporate meeting – it's a workshop tailored for the innovative minds from creative industries.",[842,74357,74358],{},"In this setting, traditional requirement gathering methods are set aside. Instead, participants dive into brainstorming sessions, sketching ideas, and quickly prototyping new concepts. The goal is to capture the raw creativity and technical insights of these professionals. The facilitator guides the flow of ideas, adapting the workshop to fit the dynamic nature of creative processes.",[842,74360,74361],{},"This article explores how to conduct software requirement gathering workshops for the creative industries. We’ll discuss why traditional methods of formal analytical workshops often fail with creative professionals and how flexibility, visual engagement, and collaboration can lead to better outcomes. Whether you're a project manager, business analyst, or facilitator, these insights will help you unlock the potential of your creative teams and make your workshops something much more fruitful.",[863,74363,74365],{"id":74364},"understanding-creative-professionals","Understanding Creative Professionals",[842,74367,74368],{},"Creative professionals often blend artistic vision with technical skill. Their approach to projects is driven by innovation, user experience, and a desire to push boundaries. Understanding their mindset and work style is crucial for conducting effective software requirement gathering workshops.",[1074,74370,74372],{"id":74371},"unique-traits-and-work-styles","Unique Traits and Work Styles",[842,74374,74375],{},"Creative professionals thrive in environments that allow for flexibility and exploration:",[1045,74377,74379,74383,74387],{"className":74378},[1048,1049,1050,1051,1052],[1054,74380],{"description":74381,"title":74382},"Rigid processes stifle creativity. Creative individuals prefer fluid approaches that let ideas evolve organically.","Value Flexibility",[1054,74384],{"description":74385,"title":74386},"They communicate through sketches, diagrams, and prototypes. Visual tools make abstract ideas tangible and easier to discuss.","Visual Thinking",[1054,74388],{"description":74389,"title":74390},"They value diverse perspectives and excel in team settings where ideas flow freely, leading to greater innovation.","Collaborative Nature",[1074,74392,74394],{"id":74393},"scientific-insights-on-creativity","Scientific Insights on Creativity",[1045,74396,74398,74402,74406],{"className":74397},[1048,1049,1050,1051,1052],[1054,74399],{"description":74400,"title":74401},"Spending time in nature boosts attention and promotes mind-wandering. Consider running part of the workshop outdoors.","Nature & Creativity",[1054,74403],{"description":74404,"title":74405},"Preparation → incubation → illumination → verification. Support each stage with relaxed spaces and quiet zones for focused thinking.","Stages of Creativity",[1054,74407],{"description":74408,"title":74409},"Creative professionals generate multiple solutions (divergent) vs. finding one answer (convergent). Workshops should encourage exploring many possibilities.","Divergent Thinking",[1074,74411,74413],{"id":74412},"challenges-with-traditional-methods","Challenges with Traditional Methods",[842,74415,74416],{},"Traditional requirement gathering methods often fail with creative professionals:",[1901,74418,74419],{},[842,74420,74421,74424],{},[996,74422,74423],{},"Common pitfalls:"," Miscommunication from rigid documentation, stifled innovation from inflexible processes, and participant disengagement from overly formal environments. Creative professionals thrive in open, dynamic, less hierarchical settings.",[863,74426,74428],{"id":74427},"tailoring-the-workshop-approach","Tailoring the Workshop Approach",[842,74430,74431],{},"To effectively engage creative professionals, it's essential to tailor the workshop approach to their unique needs and working styles. This involves incorporating flexibility, using creative tools, and designing dynamic activities that stimulate innovation and collaboration.",[1074,74433,74435],{"id":74434},"flexible-and-dynamic-activities","Flexible and Dynamic Activities",[842,74437,74438],{},"Creative professionals thrive in environments that allow their ideas to flow freely and evolve organically. Traditional, rigid structures can inhibit their creative processes, so workshops should incorporate flexible and dynamic activities.",[958,74440,74441,74444,74447],{},[961,74442,74443],{},"Brainstorming Sessions: Encourage open-ended brainstorming sessions where all ideas are welcomed without immediate critique. This approach helps generate a broad range of possibilities and fosters an inclusive environment. For example, start with a \"brain dump\" session where participants quickly jot down any idea that comes to mind on sticky notes. Then, group similar ideas together and discuss each cluster to explore potential directions.",[961,74445,74446],{},"Design Sprints: Implement short, time-boxed design sprints that focus on developing and testing ideas quickly. For instance, dedicate one day to ideation, the next to prototyping, and the third to testing with real users. This rapid cycle allows participants to see their ideas in action and make quick adjustments based on feedback.",[961,74448,74449],{},"Ideation Workshops: Organize workshops dedicated to ideation, where participants can explore and develop new concepts collaboratively. Structure the session with phases: start with individual idea generation, move to small group discussions to refine ideas, and then reconvene as a larger group to share and further develop the best concepts.",[1074,74451,74453],{"id":74452},"creative-tools-and-techniques","Creative Tools and Techniques",[1045,74455,74457,74461,74465],{"className":74456},[1048,1049,1050,1051,1052],[1054,74458],{"description":74459,"title":74460},"Provide blank templates — participants draw 6-8 frames showing how a user interacts with their proposed solution. Visualises the user journey and explores different scenarios.","Storyboarding",[1054,74462],{"description":74463,"title":74464},"Start with a central concept, branch into ideas and details. For a music app: \"Features\" → \"UI,\" \"Library,\" \"Social Sharing,\" \"Customisation.\" Explores connections systematically.","Mind Mapping",[1054,74466],{"description":74467,"title":74468},"Create low-fidelity prototypes with paper mock-ups or digital tools like Figma. Invite a UX designer to help participants build clickable prototypes for testing user flows.","Prototyping",[1074,74470,74472],{"id":74471},"engaging-the-senses","Engaging the Senses",[842,74474,74475],{},"Creative professionals respond well to multi-sensory engagement:",[1045,74477,74479,74483,74487],{"className":74478},[1048,1049,1050,1051,1052],[1054,74480],{"description":74481,"title":74482},"Use Miro or similar tools for real-time collaborative drawing and annotation. Visual collaboration clarifies complex ideas.","Visual Aids",[1054,74484],{"description":74485,"title":74486},"Create a \"user journey wall\" with moveable sticky notes. Participants rearrange steps to visualise and improve user flows.","Interactive Elements",[1054,74488],{"description":74489,"title":74490},"Comfortable seating, natural lighting, colourful posters, and breakout areas for small groups. The right environment amplifies creativity.","Ambience",[1074,74492,74494],{"id":74493},"encouraging-participation-and-collaboration","Encouraging Participation and Collaboration",[842,74496,74497],{},"Creating a collaborative environment is essential for harnessing the potential of creative professionals. Facilitators should employ techniques that encourage active participation and idea sharing.",[958,74499,74500,74503,74506],{},[961,74501,74502],{},"Role-Playing and Scenario Planning: Use role-playing and scenario planning to help participants explore different perspectives and test ideas in various contexts. For example, participants could role-play as different user personas interacting with a new music app, identifying potential pain points and areas for improvement. This technique fosters empathy and a deeper understanding of user needs.",[961,74504,74505],{},"Gamification: Incorporate gamification elements to make workshops more engaging and fun. For instance, set up a \"design challenge\" where teams compete to come up with the most innovative solution to a given problem within a limited time frame. Offer small rewards for the most creative ideas or the best teamwork to motivate participants.",[961,74507,74508],{},"Open Dialogue: Foster an atmosphere of open dialogue where all participants feel comfortable sharing their thoughts and ideas. Encourage active listening and constructive feedback by using techniques like \"round-robin\" discussions, where each participant takes a turn to speak without interruption. This ensures everyone has a voice and contributes to the collective brainstorming process.",[863,74510,74512],{"id":74511},"facilitation-techniques-for-success","Facilitation Techniques for Success",[842,74514,74515],{},"Facilitating workshops for creative professionals requires an adaptive and engaging approach to foster innovation and participation. Here are some effective techniques tailored for this audience:",[1074,74517,74519],{"id":74518},"adaptive-facilitation","Adaptive Facilitation",[842,74521,74522],{},"Effective facilitation starts with flexibility. Rather than rigidly sticking to a pre-set agenda, be prepared to adjust based on the flow of ideas. For example, if a brainstorming session sparks particularly innovative discussions, allow more time for them to unfold. This approach encourages participants to delve deeper into their creative processes without feeling constrained by time limits.",[842,74524,74525],{},"Creating an environment that encourages spontaneity is equally important. It may not be easy due to space constraints, but at least try to compensate for things with activities like real-time sketching sessions where participants can immediately visualize their ideas. This not only captures the creative spark but also helps in refining concepts collaboratively.",[1074,74527,74529],{"id":74528},"managing-time-creatively","Managing Time Creatively",[842,74531,74532],{},"Dynamic time management involves setting flexible time blocks that can be adjusted based on the group’s engagement. Allocate more time for discussions that yield rich ideas and shorten periods where creativity seems to stall. This ensures that the workshop remains productive and responsive to the participants' needs.",[842,74534,74535],{},"Incorporating regular breaks is essential. These breaks give participants time to reflect and process information. For example, short, frequent breaks where individuals can step outside, with a coffee, a cigarette or just stretching the legs and engaging in informal conversations can lead to deeper insights and more innovative solutions once the group reconvenes.",[1074,74537,74494],{"id":74538},"encouraging-participation-and-collaboration-1",[842,74540,74541],{},"Inclusive activities are crucial for gathering a wide range of ideas amongst participants. Here are some methods fostering participation and collaboration that can be used at the beginning, during and at the end of the workshop – so no one will be left behind.",[958,74543,74544],{},[961,74545,74546],{},"Picture Tales: The Picture Sorts technique is an engaging method used to uncover deeper insights and stimulate creative thinking in workshops. To arrange this activity, facilitators provide participants scissors and a variety of color magazines and other visual materials. Participants are then asked to cut out images that they feel represent specific themes or concepts related to the workshop’s objectives, such as customer experiences, desired outcomes, or emotional responses to a new product. For example, in a musictech workshop, participants might select images that they associate with user satisfaction or innovative features of a new music app. This visual and hands-on approach helps participants articulate abstract ideas and emotions that might be difficult to express through words alone.",[842,74548,74549],{},"Conducting the Picture Sorts activity involves several steps. First, facilitators introduce the task and explain the themes or questions participants should consider while selecting images. Participants are given time to browse through the visual materials and create their collages. Once completed, they present their selections to the group, explaining why they chose each image and what it represents. This often leads to rich discussions and a deeper understanding of the underlying motivations and desires of users or stakeholders. The visual nature of this technique helps break down complex concepts and promotes a more intuitive and creative exploration of the workshop topic,",[958,74551,74552],{},[961,74553,74554],{},"As an alternative to unstructured group discussion/brainstorming, a quite engaging Parallel Lines method can be used. It involves arranging participants into two lines facing each other, ensuring everyone has a partner. Each participant is assigned a specific role or perspective related to the workshop topic, such as different user personas or stakeholders in a project. For instance, one line might represent musicians while the other represents users. The pairs then engage in focused, one-on-one discussions on predefined scenarios or questions, like \"How would this new feature improve enjoyment of music?\" or \"What challenges do you anticipate with this technology?\"",[842,74556,74557],{},"After a set time, typically 5-10 minutes, one line shifts to the next partner, creating new pairs for each round and allowing diverse perspectives to emerge. This one-on-one interaction can reveal insights that might be overlooked in a larger group setting.",[958,74559,74560],{},[961,74561,74562],{},"For a summary of the results, you can use techniques like the Gallery Walk. To arrange this activity, participants are divided into small groups, each tasked with discussing a particular aspect of the workshop topic and creating a visual summary of their discussion on a flipchart or poster. These visual summaries, often including diagrams, charts, and key points, are then displayed around the room like an art gallery. For instance, in a musictech workshop, groups might explore different user interface designs or feature sets for a new music app, visually presenting their ideas for others to review.",[842,74564,74565],{},"Conducting a Gallery Walk involves several steps to ensure effectiveness. First, facilitators explain the objectives and divide participants into groups, providing them with the necessary materials and a clear timeline. After creating their visual summaries, groups post their flipcharts around the room. Participants then circulate, reviewing each group's work at their own pace. This setup allows for deeper engagement as individuals can discuss and ask questions with the group members who created the displays. One person from each group might stay with their flipchart to answer questions and provide further insights.",[842,74567,74568],{},"These methods encourage everyone to contribute and learn from different perspectives, helping even shy people present their ideas and do not allow the most extroverted participants to dominate the workshop.",[863,74570,74572],{"id":74571},"instead-of-a-summary","Instead of a Summary",[842,74574,74575],{},"Imagine the end of the workshop — the room filled with sketches, mind maps, and prototypes made of paper clips and duct tape. This wasn’t a typical meeting. It was a workshop tailored for creative minds, transforming abstract visions into something tangible.",[1032,74577,74578],{},[842,74579,74580],{},"The key ingredients: adaptive facilitation, visual tools, and collaborative techniques like Gallery Walk, Parallel Lines, and Picture Sorts. These methods foster diverse perspectives and drive user-centred outcomes that solve real problems.",{"title":728,"searchDepth":729,"depth":729,"links":74582},[74583,74588,74594,74599],{"id":74364,"depth":729,"text":74365,"children":74584},[74585,74586,74587],{"id":74371,"depth":1112,"text":74372},{"id":74393,"depth":1112,"text":74394},{"id":74412,"depth":1112,"text":74413},{"id":74427,"depth":729,"text":74428,"children":74589},[74590,74591,74592,74593],{"id":74434,"depth":1112,"text":74435},{"id":74452,"depth":1112,"text":74453},{"id":74471,"depth":1112,"text":74472},{"id":74493,"depth":1112,"text":74494},{"id":74511,"depth":729,"text":74512,"children":74595},[74596,74597,74598],{"id":74518,"depth":1112,"text":74519},{"id":74528,"depth":1112,"text":74529},{"id":74538,"depth":1112,"text":74494},{"id":74571,"depth":729,"text":74572},"2024-06-07T00:00:00.000Z","Learn how to run effective software requirement workshops for creative professionals. Tips on flexibility, visual engagement, and collaborative facilitation.",{"src":74603},"/images/blog/musictechlab_blog_how-to-conduct-workshops-for-creative-industry.webp",{"enabled":738,"items":74605},[74606,74608,74610],{"text":74607,"icon":5507},"Creative professionals need flexible, visual workshops instead of rigid requirement-gathering formats.",{"text":74609,"icon":4845},"Techniques like Gallery Walk and Parallel Lines ensure even shy participants contribute ideas.",{"text":74611,"icon":50270},"Multi-sensory engagement and adaptive facilitation unlock better outcomes than formal agendas.",{},{"title":502,"description":74601},[5523,74615],"outsourcing","6OmE_Lrk-IHwxhQ4NFP9L6TdIyeC6E4G6aQB_Qobfx4",{"id":74618,"title":237,"authors":74619,"badge":723,"body":74622,"category":5678,"client":723,"date":76084,"description":76085,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":76086,"keyTakeaways":723,"meta":76088,"navigation":738,"path":238,"seo":76089,"status":723,"stem":239,"tags":76090,"teaser":723,"__hash__":76091},"posts/blog/newsletter/music-industry-tech-openings-june-2024-update.md",[74620],{"name":50093,"to":50094,"avatar":74621},{"src":50096},{"type":725,"value":74623,"toc":76034},[74624,74626,74631,74637,74640,74642,74645,74647,74649,74654,74660,74662,74664,74667,74669,74672,74675,74677,74679,74682,74684,74686,74688,74693,74699,74702,74705,74708,74710,74713,74715,74718,74721,74723,74726,74729,74731,74733,74735,74740,74745,74747,74749,74752,74754,74757,74759,74762,74764,74766,74768,74773,74778,74780,74783,74785,74787,74789,74791,74793,74795,74800,74805,74807,74809,74811,74813,74818,74823,74825,74828,74830,74832,74834,74839,74844,74847,74850,74853,74856,74859,74862,74865,74868,74871,74874,74877,74879,74881,74884,74889,74894,74897,74899,74901,74903,74906,74908,74910,74912,74915,74920,74926,74928,74930,74932,74934,74936,74938,74940,74945,74950,74953,74956,74958,74960,74962,74967,74972,74974,74977,74979,74982,74985,74988,74991,74994,74997,74999,75002,75004,75006,75011,75016,75018,75020,75022,75024,75026,75031,75036,75038,75041,75043,75045,75047,75051,75057,75063,75065,75068,75071,75074,75076,75078,75083,75088,75090,75093,75096,75099,75102,75105,75108,75110,75112,75114,75116,75121,75123,75126,75128,75130,75133,75136,75138,75141,75143,75145,75147,75152,75157,75160,75162,75165,75167,75170,75172,75174,75179,75185,75187,75190,75193,75195,75198,75201,75204,75207,75209,75212,75214,75217,75222,75226,75228,75231,75233,75235,75240,75245,75247,75250,75252,75255,75260,75264,75267,75270,75272,75275,75278,75281,75284,75286,75288,75293,75298,75301,75304,75306,75309,75312,75314,75317,75320,75322,75325,75328,75330,75332,75334,75340,75345,75347,75349,75351,75353,75355,75357,75359,75361,75366,75370,75373,75375,75377,75379,75382,75385,75387,75389,75391,75396,75401,75403,75405,75408,75410,75414,75420,75426,75428,75431,75433,75435,75440,75445,75448,75450,75452,75454,75458,75464,75468,75470,75472,75475,75478,75480,75484,75489,75495,75498,75501,75504,75507,75510,75513,75515,75517,75519,75524,75529,75532,75535,75537,75540,75542,75544,75549,75554,75557,75560,75563,75566,75569,75571,75574,75577,75580,75583,75586,75588,75590,75595,75600,75602,75605,75607,75609,75611,75616,75622,75625,75628,75630,75633,75635,75638,75641,75644,75647,75650,75653,75655,75657,75661,75666,75668,75671,75673,75675,75680,75685,75687,75690,75692,75696,75702,75708,75711,75714,75717,75720,75723,75726,75729,75731,75734,75737,75740,75742,75744,75749,75753,75756,75758,75761,75764,75766,75768,75773,75778,75780,75783,75786,75789,75792,75795,75798,75800,75803,75805,75808,75813,75818,75820,75822,75824,75826,75831,75836,75839,75841,75844,75847,75849,75852,75855,75858,75860,75862,75864,75866,75868,75873,75878,75881,75883,75886,75888,75890,75895,75900,75902,75904,75907,75910,75913,75915,75917,75923,75927,75929,75932,75935,75937,75939,75941,75943,75945,75947,75949,75951,75953,75958,75963,75966,75968,75970,75972,75977,75982,75984,75987,75989,75991,75993,75998,76003,76006,76008,76011,76013,76015,76020,76026,76028,76030,76032],[1074,74625,55602],{"id":55599},[842,74627,74628],{},[846,74629,73573],{"href":69867,"rel":74630},[850],[842,74632,74633],{},[846,74634,74636],{"href":69871,"rel":74635},[850],"Careers Page",[842,74638,74639],{},"Location(s): 🇬🇧 🇸🇪",[842,74641,52653],{},[842,74643,74644],{},"Tech Team Manager (Marketplace Infrastructure)",[842,74646,52316],{},[1074,74648,54553],{"id":54550},[842,74650,74651],{},[846,74652,73573],{"href":54558,"rel":74653},[850],[842,74655,74656],{},[846,74657,73577],{"href":74658,"rel":74659},"https://www.aegworldwide.com/careers",[850],[842,74661,53794],{},[842,74663,57129],{},[842,74665,74666],{},"Associate Solutions Architect - AXS",[842,74668,54614],{},[842,74670,74671],{},"Senior Full-Stack Engineer (Data)",[842,74673,74674],{},"Chief Information Security Officer",[842,74676,57328],{},[842,74678,54576],{},[842,74680,74681],{},"Service Desk Technician II",[842,74683,72808],{},[842,74685,52316],{},[1074,74687,5059],{"id":66150},[842,74689,74690],{},[846,74691,73573],{"href":66155,"rel":74692},[850],[842,74694,74695],{},[846,74696,74636],{"href":74697,"rel":74698},"https://www.amazon.jobs/en/team/digital-music",[850],[842,74700,74701],{},"Location(s): 🇺🇸 🇮🇳 🇪🇪 🇲🇽",[842,74703,74704],{},"Senior Product Manager - Tech, Amazon Music",[842,74706,74707],{},"Sr. Technical Program Manager, Wondery",[842,74709,53093],{},[842,74711,74712],{},"Support Engineer",[842,74714,72517],{},[842,74716,74717],{},"Quality Assurance Engineer, Amazon Music - Growth & Marketing",[842,74719,74720],{},"Software Development Engineer II (Ruby on Rails), ART19",[842,74722,69937],{},[842,74724,74725],{},"Senior Technical Program Manager, Music Licensing",[842,74727,74728],{},"Quality Assurance Engineer II - Amazon Music, Amazon Music Subscriptions Team",[842,74730,70510],{},[842,74732,52316],{},[1074,74734,55633],{"id":55630},[842,74736,74737],{},[846,74738,73573],{"href":55638,"rel":74739},[850],[842,74741,74742],{},[846,74743,74636],{"href":55642,"rel":74744},[850],[842,74746,55646],{},[842,74748,53931],{},[842,74750,74751],{},"Senior Software Engineer / Data",[842,74753,56035],{},[842,74755,74756],{},"Senior Backend Engineer, Payments",[842,74758,72559],{},[842,74760,74761],{},"Staff Android Engineer",[842,74763,55664],{},[842,74765,52316],{},[1074,74767,54964],{"id":54961},[842,74769,74770],{},[846,74771,73573],{"href":70017,"rel":74772},[850],[842,74774,74775],{},[846,74776,74636],{"href":70021,"rel":74777},[850],[842,74779,57856],{},[842,74781,74782],{},"Full Stack Team Lead",[842,74784,52659],{},[842,74786,68978],{},[842,74788,54990],{},[842,74790,54982],{},[842,74792,52316],{},[1074,74794,58491],{"id":73671},[842,74796,74797],{},[846,74798,73573],{"href":58494,"rel":74799},[850],[842,74801,74802],{},[846,74803,74636],{"href":73683,"rel":74804},[850],[842,74806,53768],{},[842,74808,53498],{},[842,74810,52316],{},[1074,74812,53942],{"id":53939},[842,74814,74815],{},[846,74816,73573],{"href":53947,"rel":74817},[850],[842,74819,74820],{},[846,74821,74636],{"href":53951,"rel":74822},[850],[842,74824,53955],{},[842,74826,74827],{},"Senior Fullstack Developer",[842,74829,72629],{},[842,74831,52316],{},[1074,74833,55672],{"id":55669},[842,74835,74836],{},[846,74837,73573],{"href":54595,"rel":74838},[850],[842,74840,74841],{},[846,74842,74636],{"href":55680,"rel":74843},[850],[842,74845,74846],{},"Location(s): 🇨🇦 🇵🇭 🇵🇱",[842,74848,74849],{},"HTML/CMS Specialist",[842,74851,74852],{},"(Java) Test Automation Developer, Audio Products",[842,74854,74855],{},"Technical Writer",[842,74857,74858],{},"Technical Support Engineer I – Interplay",[842,74860,74861],{},"Senior System Business Analyst, Salesforce",[842,74863,74864],{},"Cloud System Engineer",[842,74866,74867],{},"C++ Tools and Infrastructure Developer, Audio Products",[842,74869,74870],{},"Software Developer, Audio / Music Software",[842,74872,74873],{},"Audio Software Tester / QA engineer",[842,74875,74876],{},"Cyber Security Analyst",[842,74878,70510],{},[842,74880,52316],{},[1074,74882,58822],{"id":74883},"bandlab-technologies",[842,74885,74886],{},[846,74887,73573],{"href":54503,"rel":74888},[850],[842,74890,74891],{},[846,74892,74636],{"href":54507,"rel":74893},[850],[842,74895,74896],{},"Location(s): 🇵🇭 🇸🇬",[842,74898,70153],{},[842,74900,70159],{},[842,74902,72711],{},[842,74904,74905],{},"Frontend Engineer, Creation team",[842,74907,58844],{},[842,74909,54985],{},[842,74911,52316],{},[1074,74913,69205],{"id":74914},"beatdapp",[842,74916,74917],{},[846,74918,73573],{"href":69208,"rel":74919},[850],[842,74921,74922],{},[846,74923,74636],{"href":74924,"rel":74925},"https://beatdapp.breezy.hr/",[850],[842,74927,54397],{},[842,74929,69216],{},[842,74931,53093],{},[842,74933,68978],{},[842,74935,52501],{},[842,74937,52316],{},[1074,74939,55209],{"id":55206},[842,74941,74942],{},[846,74943,73573],{"href":55214,"rel":74944},[850],[842,74946,74947],{},[846,74948,74636],{"href":56634,"rel":74949},[850],[842,74951,74952],{},"Location(s): 🇺🇸 🌐",[842,74954,74955],{},"Backend Engineer (Contractor)",[842,74957,54570],{},[842,74959,52316],{},[1074,74961,56369],{"id":56366},[842,74963,74964],{},[846,74965,73573],{"href":56374,"rel":74966},[850],[842,74968,74969],{},[846,74970,74636],{"href":56378,"rel":74971},[850],[842,74973,53955],{},[842,74975,74976],{},"Head of Supply Chain Product",[842,74978,54570],{},[842,74980,74981],{},"Lead Developer",[842,74983,74984],{},"Security engineer",[842,74986,74987],{},"Head of Operational Excellence",[842,74989,74990],{},"Business Analyst / Data Analyst",[842,74992,74993],{},"Senior Front-End Software Engineer",[842,74995,74996],{},"Senior Data engineer",[842,74998,52504],{},[842,75000,75001],{},"Customer Reporting Specialist",[842,75003,52316],{},[1074,75005,53137],{"id":53134},[842,75007,75008],{},[846,75009,73573],{"href":53142,"rel":75010},[850],[842,75012,75013],{},[846,75014,74636],{"href":53146,"rel":75015},[850],[842,75017,56240],{},[842,75019,70242],{},[842,75021,54093],{},[842,75023,52316],{},[1074,75025,53630],{"id":53627},[842,75027,75028],{},[846,75029,73573],{"href":53635,"rel":75030},[850],[842,75032,75033],{},[846,75034,74636],{"href":53639,"rel":75035},[850],[842,75037,54327],{},[842,75039,75040],{},"Frond-End Developer",[842,75042,53652],{},[842,75044,53655],{},[842,75046,52316],{},[1074,75048,75050],{"id":75049},"colossal","Colossal",[842,75052,75053],{},[846,75054,73573],{"href":75055,"rel":75056},"https://www.linkedin.com/company/colossalsound/",[850],[842,75058,75059],{},[846,75060,74636],{"href":75061,"rel":75062},"https://colossalsound.notion.site/Colossal-is-Where-Music-Begins-b94c7696569b4d6e9b3d4d2b497bbd50",[850],[842,75064,53768],{},[842,75066,75067],{},"Senior Backend Engineer - Audio Recommendations, Data",[842,75069,75070],{},"Senior Backend Engineer - Core Services + APIs",[842,75072,75073],{},"Senior/Lead Product Designer - App, Web",[842,75075,52316],{},[1074,75077,40953],{"id":55281},[842,75079,75080],{},[846,75081,73573],{"href":55288,"rel":75082},[850],[842,75084,75085],{},[846,75086,74636],{"href":55292,"rel":75087},[850],[842,75089,53955],{},[842,75091,75092],{},"IT Support Apprentice",[842,75094,75095],{},"iOS Engineer - User growth Team (Internship)",[842,75097,75098],{},"Analytics Engineer Apprenticeship",[842,75100,75101],{},"IT Infrastructure Engineer",[842,75103,75104],{},"Data Analyst - Operational Performance Team",[842,75106,75107],{},"QA Analyst Intern - Industry",[842,75109,54118],{},[842,75111,52316],{},[1074,75113,57251],{"id":57248},[842,75115,73573],{},[842,75117,75118],{},[846,75119,74636],{"href":70312,"rel":75120},[850],[842,75122,52307],{},[842,75124,75125],{},"Senior/Mid Software Engineer, Porting and Optimization",[842,75127,70322],{},[842,75129,70344],{},[842,75131,75132],{},"Senior Staff Imaging Software Engineer",[842,75134,75135],{},"Imaging Software Engineer- Dolby Vision",[842,75137,69293],{},[842,75139,75140],{},"Sr. Staff Researcher, Imaging",[842,75142,75132],{},[842,75144,52316],{},[1074,75146,55153],{"id":55150},[842,75148,75149],{},[846,75150,73573],{"href":55158,"rel":75151},[850],[842,75153,75154],{},[846,75155,74636],{"href":55162,"rel":75156},[850],[842,75158,75159],{},"Location(s): 🇪🇺 🇬🇧",[842,75161,58205],{},[842,75163,75164],{},"Full-Stack Engineer",[842,75166,72880],{},[842,75168,75169],{},"Technical Account Manager",[842,75171,52316],{},[1074,75173,59250],{"id":66676},[842,75175,75176],{},[846,75177,73573],{"href":59253,"rel":75178},[850],[842,75180,75181],{},[846,75182,74636],{"href":75183,"rel":75184},"https://epidemic-sound.teamtailor.com/#jobs",[850],[842,75186,55615],{},[842,75188,75189],{},"Senior Data Scientist, Commercial Analytics",[842,75191,75192],{},"Salesforce Administrator",[842,75194,59273],{},[842,75196,75197],{},"Engineering Manager - Growth",[842,75199,75200],{},"Senior Data Engineer - Data Infrastructure",[842,75202,75203],{},"Senior Data Engineer - Analytics Engineering",[842,75205,75206],{},"Signal Processing Engineer - Audio",[842,75208,52376],{},[842,75210,75211],{},"Senior Fullstack Engineer",[842,75213,52316],{},[1074,75215,68946],{"id":75216},"fm",[842,75218,75219],{},[846,75220,73573],{"href":68949,"rel":75221},[850],[842,75223,75224],{},[846,75225,74636],{"href":728},[842,75227,74952],{},[842,75229,75230],{},"Senior Software Engineer, Front End",[842,75232,52316],{},[1074,75234,12942],{"id":53015},[842,75236,75237],{},[846,75238,73573],{"href":53022,"rel":75239},[850],[842,75241,75242],{},[846,75243,74636],{"href":53026,"rel":75244},[850],[842,75246,54327],{},[842,75248,75249],{},"Ruby Engineer",[842,75251,52316],{},[1074,75253,69371],{"id":75254},"kobalt-music",[842,75256,75257],{},[846,75258,73573],{"href":58769,"rel":75259},[850],[842,75261,75262],{},[846,75263,74636],{"href":728},[842,75265,75266],{},"Location(s): 🇬🇧 🇮🇪 🇺🇸",[842,75268,75269],{},"Software Engineering Manager - Catalogues",[842,75271,69673],{},[842,75273,75274],{},"Fullstack Engineer - Catalogue Registrations",[842,75276,75277],{},"Frontend Engineer - Royalty Calculation",[842,75279,75280],{},"Director of Engineering",[842,75282,75283],{},"Data Analyst, Tracking & Business Intelligence",[842,75285,52316],{},[1074,75287,52701],{"id":52698},[842,75289,75290],{},[846,75291,73573],{"href":66612,"rel":75292},[850],[842,75294,75295],{},[846,75296,74636],{"href":70470,"rel":75297},[850],[842,75299,75300],{},"Location(s): 🇺🇸 🇨🇦 🇬🇧 🇧🇷 🇹🇭",[842,75302,75303],{},"Sr. Mobile Engineer",[842,75305,52376],{},[842,75307,75308],{},"AWS Databricks Administrator",[842,75310,75311],{},"Intermediate Mobile Developer, 10 Month Contract",[842,75313,57563],{},[842,75315,75316],{},"VP, Head of Abuse Prevention",[842,75318,75319],{},"Senior NoSQL Database Engineer",[842,75321,70495],{},[842,75323,75324],{},"Platform Engineer, 12 Month Contract",[842,75326,75327],{},"Principal Enterprise Architect",[842,75329,70008],{},[842,75331,52316],{},[1074,75333,54468],{"id":54465},[842,75335,75336],{},[846,75337,73573],{"href":75338,"rel":75339},"https://www.linkedin.com/company/pond5/",[850],[842,75341,75342],{},[846,75343,74636],{"href":54477,"rel":75344},[850],[842,75346,53794],{},[842,75348,52504],{},[842,75350,70532],{},[842,75352,69580],{},[842,75354,54990],{},[842,75356,55816],{},[842,75358,52316],{},[1074,75360,57107],{"id":57104},[842,75362,75363],{},[846,75364,73573],{"href":57112,"rel":75365},[850],[842,75367,75368],{},[846,75369,74636],{"href":728},[842,75371,75372],{},"Location(s): 🇨🇾",[842,75374,58805],{},[842,75376,54026],{},[842,75378,53498],{},[842,75380,75381],{},"Data Business Analyst",[842,75383,75384],{},"React Native Developer",[842,75386,73043],{},[842,75388,52316],{},[1074,75390,55848],{"id":55845},[842,75392,75393],{},[846,75394,73573],{"href":55853,"rel":75395},[850],[842,75397,75398],{},[846,75399,74636],{"href":55857,"rel":75400},[850],[842,75402,53900],{},[842,75404,56329],{},[842,75406,75407],{},"Systems Engineering Team Lead",[842,75409,52316],{},[1074,75411,75413],{"id":75412},"open-ticketing-ecosystem","OPEN Ticketing Ecosystem",[842,75415,75416],{},[846,75417,73573],{"href":75418,"rel":75419},"https://www.linkedin.com/company/openticketingecosystem/",[850],[842,75421,75422],{},[846,75423,74636],{"href":75424,"rel":75425},"https://careers.get-protocol.io/",[850],[842,75427,54327],{},[842,75429,75430],{},"Protocol Tech Lead",[842,75432,52316],{},[1074,75434,55430],{"id":55427},[842,75436,75437],{},[846,75438,73573],{"href":55435,"rel":75439},[850],[842,75441,75442],{},[846,75443,74636],{"href":70630,"rel":75444},[850],[842,75446,75447],{},"Location(s): 🇩🇰 🇱🇹 🇪🇸 🇩🇪",[842,75449,54118],{},[842,75451,55656],{},[842,75453,52316],{},[1074,75455,75457],{"id":75456},"ppl","PPL",[842,75459,75460],{},[846,75461,73573],{"href":75462,"rel":75463},"https://www.linkedin.com/company/ppl_3/",[850],[842,75465,75466],{},[846,75467,74636],{"href":728},[842,75469,53768],{},[842,75471,53093],{},[842,75473,75474],{},"Training Lead",[842,75476,75477],{},"Business Change Manager",[842,75479,52316],{},[1074,75481,75483],{"id":75482},"pond5","Pond5",[842,75485,75486],{},[846,75487,73573],{"href":75338,"rel":75488},[850],[842,75490,75491],{},[846,75492,74636],{"href":75493,"rel":75494},"https://explore.pond5.com/careers/",[850],[842,75496,75497],{},"Location(s): 🇺🇸 🇨🇦 🇮🇪",[842,75499,75500],{},"Project Manager, Transformation Office",[842,75502,75503],{},"Enterprise Integration Architect",[842,75505,75506],{},"Senior Salesforce Business Analyst",[842,75508,75509],{},"Director, Sales / Ops Analytics & Data Ops Lead",[842,75511,75512],{},"Senior Workday Engineer",[842,75514,58802],{},[842,75516,52316],{},[1074,75518,55515],{"id":55512},[842,75520,75521],{},[846,75522,73573],{"href":55520,"rel":75523},[850],[842,75525,75526],{},[846,75527,74636],{"href":55524,"rel":75528},[850],[842,75530,75531],{},"Location(s): 🇷🇴 🇪🇸 🇵🇹",[842,75533,75534],{},"Director of Customer Experience",[842,75536,54570],{},[842,75538,75539],{},"Software Team Lead (.Net Core)",[842,75541,52316],{},[1074,75543,70650],{"id":70649},[842,75545,75546],{},[846,75547,73573],{"href":70655,"rel":75548},[850],[842,75550,75551],{},[846,75552,74636],{"href":70659,"rel":75553},[850],[842,75555,75556],{},"Location(s): 🇩🇪 🇨🇭",[842,75558,75559],{},"Product Security Engineer",[842,75561,75562],{},"Manager IT Customer Service Center",[842,75564,75565],{},"Network Audio Driver Engineer",[842,75567,75568],{},"Software Engineer - Backend Services",[842,75570,54717],{},[842,75572,75573],{},"Information Security Manager",[842,75575,75576],{},"Product Information Manager",[842,75578,75579],{},"Director of Modules and Platforms",[842,75581,75582],{},"Service Techniker",[842,75584,75585],{},"Product Manager Website",[842,75587,52316],{},[1074,75589,56343],{"id":56340},[842,75591,75592],{},[846,75593,73573],{"href":56348,"rel":75594},[850],[842,75596,75597],{},[846,75598,74636],{"href":66866,"rel":75599},[850],[842,75601,56356],{},[842,75603,75604],{},"Internships - Software Engineers",[842,75606,73151],{},[842,75608,52316],{},[1074,75610,54413],{"id":54410},[842,75612,75613],{},[846,75614,73573],{"href":54418,"rel":75615},[850],[842,75617,75618],{},[846,75619,74636],{"href":75620,"rel":75621},"https://careers.siriusxm.com/careers/jobs",[850],[842,75623,75624],{},"Location(s): 🇺🇸 🇷🇴 🇬🇧",[842,75626,75627],{},"Senior Software Engineer, ServiceNow",[842,75629,73176],{},[842,75631,75632],{},"Staff Data Center Engineer / Linux Administration",[842,75634,52653],{},[842,75636,75637],{},"Senior DevOps Engineer - Agency Temp",[842,75639,75640],{},"Data Center Engineer III",[842,75642,75643],{},"Director, Systems Engineering - Atlassian",[842,75645,75646],{},"Senior Technical Program Manager",[842,75648,75649],{},"IT Asset Manager",[842,75651,75652],{},"Oracle Application Developer",[842,75654,52316],{},[1074,75656,53200],{"id":53197},[842,75658,75659],{},[846,75660,73573],{"href":728},[842,75662,75663],{},[846,75664,74636],{"href":53209,"rel":75665},[850],[842,75667,53768],{},[842,75669,75670],{},"Developer",[842,75672,52316],{},[1074,75674,56227],{"id":56224},[842,75676,75677],{},[846,75678,73573],{"href":56232,"rel":75679},[850],[842,75681,75682],{},[846,75683,74636],{"href":56236,"rel":75684},[850],[842,75686,56240],{},[842,75688,75689],{},"Head Of Product",[842,75691,52316],{},[1074,75693,75695],{"id":75694},"sonos","Sonos",[842,75697,75698],{},[846,75699,73573],{"href":75700,"rel":75701},"https://www.linkedin.com/company/sonos-inc-/",[850],[842,75703,75704],{},[846,75705,74636],{"href":75706,"rel":75707},"https://sonos.wd1.myworkdayjobs.com/en-US/Sonos/details/Senior-Backend-Engineer---APIs---Tools_R2140?jobFamilyGroup=f4abac2e193c0139c3e224c5801bbb95&jobFamilyGroup=f4abac2e193c01e37db917c5801bb895&jobFamilyGroup=f4abac2e193c01bd0c1ea1c5801bd395&jobFamilyGroup=f4abac2e193c01a46d0ae8c4801baf95&jobFamilyGroup=f4abac2e193c01cb8cade1c4801bad95",[850],[842,75709,75710],{},"Location(s): 🇬🇧 🇺🇸 🇨🇳",[842,75712,75713],{},"Audio Technology Researcher",[842,75715,75716],{},"Senior Software Embedded Engineer",[842,75718,75719],{},"Senior Mechanical Design Engineer",[842,75721,75722],{},"Power Electronics Engineer - Wearables",[842,75724,75725],{},"Audio Research Scientist",[842,75727,75728],{},"Senior Backend Engineer - APIs & Tools",[842,75730,52779],{},[842,75732,75733],{},"Senior Machine Learning Engineer - Automatic Speech Recognition",[842,75735,75736],{},"Lead Hardware Verification Engineer",[842,75738,75739],{},"Industrial and Lean Engineer",[842,75741,52316],{},[1074,75743,66830],{"id":66829},[842,75745,75746],{},[846,75747,73573],{"href":66835,"rel":75748},[850],[842,75750,75751],{},[846,75752,74636],{"href":728},[842,75754,75755],{},"Location(s): 🇬🇧 🇺🇸 🇩🇪",[842,75757,53037],{},[842,75759,75760],{},"Senior Backend Engineer, Media Streaming",[842,75762,75763],{},"Senior Backend Software Engineer, Ads Team",[842,75765,52316],{},[1074,75767,57880],{"id":57877},[842,75769,75770],{},[846,75771,73573],{"href":57885,"rel":75772},[850],[842,75774,75775],{},[846,75776,74636],{"href":57891,"rel":75777},[850],[842,75779,53794],{},[842,75781,75782],{},"Lead Software Engineer - New Business and Technology",[842,75784,75785],{},"Lead Software Engineer, Rights and Repertoire teams",[842,75787,75788],{},"Senior Software Engineer, Portals",[842,75790,75791],{},"Manager, Cybersecurity Engineering",[842,75793,75794],{},"Senior Software Engineer, Distribution and/or Music Publishing Rights Team",[842,75796,75797],{},"Tech Strategy and Planning Analyst",[842,75799,57901],{},[842,75801,75802],{},"Manager, Tech Strategy and Planning",[842,75804,52316],{},[1074,75806,69704],{"id":75807},"soundtrack-your-brand",[842,75809,75810],{},[846,75811,73573],{"href":69709,"rel":75812},[850],[842,75814,75815],{},[846,75816,74636],{"href":69713,"rel":75817},[850],[842,75819,55615],{},[842,75821,59302],{},[842,75823,52316],{},[1074,75825,5070],{"id":12966},[842,75827,75828],{},[846,75829,73573],{"href":66516,"rel":75830},[850],[842,75832,75833],{},[846,75834,74636],{"href":73272,"rel":75835},[850],[842,75837,75838],{},"Location(s): 🇬🇧 🇺🇸 🇸🇪",[842,75840,53037],{},[842,75842,75843],{},"Senior Product Manager, Content Understanding",[842,75845,75846],{},"Senior Security Engineer, Detection and Response",[842,75848,73296],{},[842,75850,75851],{},"Full-Stack Engineer, Consumer Experience",[842,75853,75854],{},"Senior Machine Learning Engineering Manager, Personalization",[842,75856,75857],{},"Senior Security GRC Manager",[842,75859,73287],{},[842,75861,58997],{},[842,75863,52653],{},[842,75865,52316],{},[1074,75867,55872],{"id":55869},[842,75869,75870],{},[846,75871,73573],{"href":52483,"rel":75872},[850],[842,75874,75875],{},[846,75876,74636],{"href":73316,"rel":75877},[850],[842,75879,75880],{},"Location(s): 🇺🇸 🇦🇺",[842,75882,52504],{},[842,75884,75885],{},"Lead Machine Learning Researcher",[842,75887,52316],{},[1074,75889,56962],{"id":56959},[842,75891,75892],{},[846,75893,73573],{"href":56967,"rel":75894},[850],[842,75896,75897],{},[846,75898,74636],{"href":74120,"rel":75899},[850],[842,75901,53794],{},[842,75903,53093],{},[842,75905,75906],{},"Senior Experimentation Lead, Product",[842,75908,75909],{},"Senior Product Manager, Enterprise Products",[842,75911,75912],{},"Senior Product Manager, Experimentation",[842,75914,52316],{},[1074,75916,26104],{"id":57602},[842,75918,75919],{},[846,75920,73573],{"href":75921,"rel":75922},"https://www.linkedin.com/company/suno-ai/",[850],[842,75924,75925],{},[846,75926,74636],{"href":728},[842,75928,53794],{},[842,75930,75931],{},"Lead Product Designer, Mobile",[842,75933,75934],{},"Lead Brand Designer",[842,75936,57654],{},[842,75938,66813],{},[842,75940,54490],{},[842,75942,57643],{},[842,75944,52626],{},[842,75946,57648],{},[842,75948,57651],{},[842,75950,52316],{},[1074,75952,74158],{"id":74155},[842,75954,75955],{},[846,75956,73573],{"href":54213,"rel":75957},[850],[842,75959,75960],{},[846,75961,74636],{"href":54217,"rel":75962},[850],[842,75964,75965],{},"Location(s): 🇨🇴 🇨🇱 🇲🇽",[842,75967,54226],{},[842,75969,52316],{},[1074,75971,59050],{"id":74188},[842,75973,75974],{},[846,75975,73573],{"href":59053,"rel":75976},[850],[842,75978,75979],{},[846,75980,74636],{"href":73374,"rel":75981},[850],[842,75983,54327],{},[842,75985,75986],{},"Software Engineer Backend (PHP)",[842,75988,55776],{},[842,75990,52316],{},[1074,75992,5037],{"id":74172},[842,75994,75995],{},[846,75996,73573],{"href":73386,"rel":75997},[850],[842,75999,76000],{},[846,76001,74636],{"href":73390,"rel":76002},[850],[842,76004,76005],{},"Location(s): 🇺🇸 🇳🇴 🇩🇪 🇬🇧",[842,76007,52623],{},[842,76009,76010],{},"WebApp Developer, Artist Tools",[842,76012,52316],{},[1074,76014,74253],{"id":74250},[842,76016,76017],{},[846,76018,73573],{"href":74260,"rel":76019},[850],[842,76021,76022],{},[846,76023,74636],{"href":76024,"rel":76025},"https://recruiting.paylocity.com/recruiting/jobs/All/80ddcdba-4539-47ff-8be2-0cf6d4d74936/Vydia-Inc",[850],[842,76027,53794],{},[842,76029,57594],{},[842,76031,73437],{},[842,76033,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":76035},[76036,76037,76038,76039,76040,76041,76042,76043,76044,76045,76046,76047,76048,76049,76050,76051,76052,76053,76054,76055,76056,76057,76058,76059,76060,76061,76062,76063,76064,76065,76066,76067,76068,76069,76070,76071,76072,76073,76074,76075,76076,76077,76078,76079,76080,76081,76082,76083],{"id":55599,"depth":1112,"text":55602},{"id":54550,"depth":1112,"text":54553},{"id":66150,"depth":1112,"text":5059},{"id":55630,"depth":1112,"text":55633},{"id":54961,"depth":1112,"text":54964},{"id":73671,"depth":1112,"text":58491},{"id":53939,"depth":1112,"text":53942},{"id":55669,"depth":1112,"text":55672},{"id":74883,"depth":1112,"text":58822},{"id":74914,"depth":1112,"text":69205},{"id":55206,"depth":1112,"text":55209},{"id":56366,"depth":1112,"text":56369},{"id":53134,"depth":1112,"text":53137},{"id":53627,"depth":1112,"text":53630},{"id":75049,"depth":1112,"text":75050},{"id":55281,"depth":1112,"text":40953},{"id":57248,"depth":1112,"text":57251},{"id":55150,"depth":1112,"text":55153},{"id":66676,"depth":1112,"text":59250},{"id":75216,"depth":1112,"text":68946},{"id":53015,"depth":1112,"text":12942},{"id":75254,"depth":1112,"text":69371},{"id":52698,"depth":1112,"text":52701},{"id":54465,"depth":1112,"text":54468},{"id":57104,"depth":1112,"text":57107},{"id":55845,"depth":1112,"text":55848},{"id":75412,"depth":1112,"text":75413},{"id":55427,"depth":1112,"text":55430},{"id":75456,"depth":1112,"text":75457},{"id":75482,"depth":1112,"text":75483},{"id":55512,"depth":1112,"text":55515},{"id":70649,"depth":1112,"text":70650},{"id":56340,"depth":1112,"text":56343},{"id":54410,"depth":1112,"text":54413},{"id":53197,"depth":1112,"text":53200},{"id":56224,"depth":1112,"text":56227},{"id":75694,"depth":1112,"text":75695},{"id":66829,"depth":1112,"text":66830},{"id":57877,"depth":1112,"text":57880},{"id":75807,"depth":1112,"text":69704},{"id":12966,"depth":1112,"text":5070},{"id":55869,"depth":1112,"text":55872},{"id":56959,"depth":1112,"text":56962},{"id":57602,"depth":1112,"text":26104},{"id":74155,"depth":1112,"text":74158},{"id":74188,"depth":1112,"text":59050},{"id":74172,"depth":1112,"text":5037},{"id":74250,"depth":1112,"text":74253},"2024-06-06T00:00:00.000Z","Curated list of tech job openings in the music industry for June 2024. Explore roles at music startups, labels, streaming platforms, and more.",{"src":76087},"/images/blog/musictechlab_blog_music-industry-tech-openings-june-2024-update.webp",{},{"title":237,"description":76085},[5678,5523],"Or3zjNO1HPMGqCsxUMEUzgtE_Rjg9eJzjmK9bToh9X8",{"id":76093,"title":245,"authors":76094,"badge":723,"body":76097,"category":5678,"client":723,"date":77550,"description":77551,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":77552,"keyTakeaways":723,"meta":77554,"navigation":738,"path":246,"seo":77555,"status":723,"stem":247,"tags":77556,"teaser":723,"__hash__":77557},"posts/blog/newsletter/music-industry-tech-openings-may-2024-update.md",[76095],{"name":50093,"to":50094,"avatar":76096},{"src":50096},{"type":725,"value":76098,"toc":77484},[76099,76104,76109,76115,76135,76138,76140,76145,76151,76156,76182,76185,76187,76192,76198,76203,76219,76222,76224,76226,76232,76235,76247,76264,76267,76269,76271,76274,76284,76290,76304,76307,76309,76314,76320,76325,76337,76340,76342,76344,76347,76354,76360,76364,76367,76369,76371,76378,76383,76393,76396,76398,76400,76403,76413,76419,76433,76436,76438,76440,76443,76453,76459,76467,76470,76472,76474,76477,76482,76488,76496,76499,76501,76504,76511,76517,76537,76540,76542,76544,76547,76558,76564,76569,76572,76574,76576,76579,76584,76590,76630,76633,76635,76640,76643,76650,76656,76663,76666,76668,76673,76679,76684,76712,76715,76717,76723,76729,76736,76739,76741,76749,76755,76761,76767,76774,76777,76779,76784,76790,76795,76801,76805,76808,76810,76815,76821,76826,76832,76836,76839,76841,76847,76856,76862,76879,76882,76884,76889,76895,76904,76907,76909,76913,76916,76927,76933,76938,76941,76943,76948,76954,76960,76963,76965,76967,76973,76979,76983,76986,76988,76994,76999,77005,77039,77042,77044,77046,77053,77073,77076,77078,77083,77089,77094,77100,77103,77105,77110,77119,77125,77129,77131,77133,77138,77144,77149,77155,77165,77168,77170,77175,77181,77186,77192,77208,77210,77212,77217,77223,77229,77231,77233,77235,77241,77246,77259,77262,77264,77266,77272,77280,77282,77284,77290,77296,77301,77307,77311,77314,77316,77321,77327,77332,77338,77344,77347,77349,77351,77357,77363,77365,77367,77371,77374,77380,77388,77392,77395,77397,77405,77412,77417,77423,77428,77430,77432,77434,77441,77450,77452,77454,77459,77465,77470,77476,77480,77482],[1074,76100,54553,76101],{"id":54550},[846,76102,52316],{"href":54558,"rel":76103},[850],[76105,76106,76108],"h5",{"id":76107},"linkedin-profile","[LinkedIn Profile",[842,76110,76111,76112,1410],{},"Career Page](",[846,76113,54558],{"href":54558,"rel":76114},[850],[958,76116,76117,76120,76122,76124,76126,76128,76131,76133],{},[961,76118,76119],{},"Software Development Manager",[961,76121,74666],{},[961,76123,54614],{},[961,76125,74671],{},[961,76127,74674],{},[961,76129,76130],{},"Service Desk Technician",[961,76132,54576],{},[961,76134,72808],{},[842,76136,76137],{},"Location(s): U.S.",[842,76139,52316],{},[1074,76141,5059,76142],{"id":66150},[846,76143,52316],{"href":66155,"rel":76144},[850],[76105,76146,76148],{"id":76147},"linkedin-profile-1",[846,76149,73573],{"href":66155,"rel":76150},[850],[842,76152,76153],{},[846,76154,73577],{"href":74697,"rel":76155},[850],[958,76157,76158,76161,76163,76166,76169,76172,76175,76178,76180],{},[961,76159,76160],{},"Software Development Engineer, Music",[961,76162,53093],{},[961,76164,76165],{},"Business Intelligence Engineer",[961,76167,76168],{},"Senior Software Development Engineer (Ruby)",[961,76170,76171],{},"Software Development Engineer (Scala/Ruby)",[961,76173,76174],{},"Support Engineer, Visual Platform Team",[961,76176,76177],{},"Quality Assurance Engineer, Amazon Music",[961,76179,74712],{},[961,76181,69937],{},[842,76183,76184],{},"Location(s): U.S., Estonia, Mexico, India & China",[842,76186,52316],{},[1074,76188,54964,76189],{"id":54961},[846,76190,52316],{"href":70017,"rel":76191},[850],[76105,76193,76195],{"id":76194},"linkedin-profile-2",[846,76196,73573],{"href":70017,"rel":76197},[850],[842,76199,76200],{},[846,76201,73577],{"href":70021,"rel":76202},[850],[958,76204,76205,76207,76209,76212,76214,76216],{},[961,76206,58802],{},[961,76208,74782],{},[961,76210,76211],{},"Full Stack Team Developer",[961,76213,52659],{},[961,76215,54982],{},[961,76217,76218],{},"Junior QA Engineer",[842,76220,76221],{},"Location(s): Israel",[842,76223,52316],{},[1074,76225,55672],{"id":55669},[76105,76227,76229],{"id":76228},"linkedin-profile-3",[846,76230,73573],{"href":54595,"rel":76231},[850],[76105,76233,76234],{"id":728},"[‍",[842,76236,76237,76238,76243,76244,1410],{},"](",[846,76239,76242],{"href":76240,"rel":76241},"https://www.linkedin.com/company/avid-technology/)%5BCareer",[850],"https://www.linkedin.com/company/avid-technology/)[Career"," Page](",[846,76245,55680],{"href":55680,"rel":76246},[850],[958,76248,76249,76251,76253,76255,76258,76261],{},[961,76250,74864],{},[961,76252,52470],{},[961,76254,74867],{},[961,76256,76257],{},"System Engineer",[961,76259,76260],{},"Front-end Web Developer/Producer",[961,76262,76263],{},"Domo Developer",[842,76265,76266],{},"Location(s): Philippines & Poland",[842,76268,52316],{},[1074,76270,53137],{"id":53134},[76105,76272,76108],{"id":76273},"linkedin-profile-4",[842,76275,76237,76276,76237,76281,1410],{},[846,76277,76280],{"href":76278,"rel":76279},"https://www.linkedin.com/company/bmat/)%5B%E2%80%8D",[850],"https://www.linkedin.com/company/bmat/)[‍",[846,76282,53146],{"href":53146,"rel":76283},[850],[76105,76285,76287],{"id":76286},"career-page",[846,76288,73577],{"href":53146,"rel":76289},[850],[958,76291,76292,76295,76298,76301],{},[961,76293,76294],{},"Research Engineer (Spain)",[961,76296,76297],{},"DevOps Engineer (Spain)",[961,76299,76300],{},"IT Operations Engineer (Spain)",[961,76302,76303],{},"Software Engineer (Python) (Spain)",[842,76305,76306],{},"Location(s): Spain",[842,76308,52316],{},[1074,76310,58822,76311],{"id":74883},[846,76312,52316],{"href":54503,"rel":76313},[850],[76105,76315,76317],{"id":76316},"linkedin-profile-5",[846,76318,73573],{"href":54503,"rel":76319},[850],[842,76321,76322],{},[846,76323,73577],{"href":54507,"rel":76324},[850],[958,76326,76327,76329,76331,76333,76335],{},[961,76328,70153],{},[961,76330,70159],{},[961,76332,72711],{},[961,76334,74905],{},[961,76336,58844],{},[842,76338,76339],{},"Location(s): Singapore",[842,76341,52316],{},[1074,76343,55209],{"id":55206},[76105,76345,76108],{"id":76346},"linkedin-profile-6",[842,76348,76349,76350],{},"]()",[846,76351,52316],{"href":76352,"rel":76353},"https://careers.beatstars.com/job?id=4341657006",[850],[76105,76355,76357],{"id":76356},"career-page-1",[846,76358,73577],{"href":76352,"rel":76359},[850],[958,76361,76362],{},[961,76363,74955],{},[842,76365,76366],{},"Location(s): LATAM",[842,76368,52316],{},[1074,76370,69205],{"id":74914},[76105,76372,76374],{"id":76373},"linkedin-page",[846,76375,76377],{"href":69208,"rel":76376},[850],"LinkedIn Page",[842,76379,76380],{},[846,76381,73577],{"href":74924,"rel":76382},[850],[958,76384,76385,76387,76389,76391],{},[961,76386,69216],{},[961,76388,52501],{},[961,76390,53093],{},[961,76392,68978],{},[842,76394,76395],{},"Location(s): Canada",[842,76397,52316],{},[1074,76399,56369],{"id":56366},[76105,76401,76108],{"id":76402},"linkedin-profile-7",[842,76404,76237,76405,76237,76410,1410],{},[846,76406,76409],{"href":76407,"rel":76408},"https://www.linkedin.com/company/believeglobal/)%5B%E2%80%8D",[850],"https://www.linkedin.com/company/believeglobal/)[‍",[846,76411,56378],{"href":56378,"rel":76412},[850],[76105,76414,76416],{"id":76415},"career-page-2",[846,76417,73577],{"href":56378,"rel":76418},[850],[958,76420,76421,76424,76426,76428,76431],{},[961,76422,76423],{},"Sr Software Engineer",[961,76425,74993],{},[961,76427,52376],{},[961,76429,76430],{},"Front-End Software Engineer",[961,76432,74996],{},[842,76434,76435],{},"Location(s): U.S. & France",[842,76437,52316],{},[1074,76439,53630],{"id":53627},[76105,76441,76108],{"id":76442},"linkedin-profile-8",[842,76444,76237,76445,76237,76450,1410],{},[846,76446,76449],{"href":76447,"rel":76448},"https://www.linkedin.com/company/collabhouse/)%5B%E2%80%8D",[850],"https://www.linkedin.com/company/collabhouse/)[‍",[846,76451,53639],{"href":53639,"rel":76452},[850],[76105,76454,76456],{"id":76455},"career-page-3",[846,76457,73577],{"href":53639,"rel":76458},[850],[958,76460,76461,76463,76465],{},[961,76462,75040],{},[961,76464,53652],{},[961,76466,53655],{},[842,76468,76469],{},"Location(s): Netherlands",[842,76471,52316],{},[1074,76473,75050],{"id":75049},[76105,76475,76108],{"id":76476},"linkedin-profile-9",[842,76478,76237,76479,1410],{},[846,76480,75055],{"href":75055,"rel":76481},[850],[76105,76483,76485],{"id":76484},"career-page-4",[846,76486,73577],{"href":75061,"rel":76487},[850],[958,76489,76490,76492,76494],{},[961,76491,75067],{},[961,76493,75070],{},[961,76495,75073],{},[842,76497,76498],{},"Location(s): U.K.",[1074,76500,40953],{"id":55281},[76105,76502,76108],{"id":76503},"linkedin-profile-10",[842,76505,76237,76506],{},[846,76507,76510],{"href":76508,"rel":76509},"https://www.linkedin.com/company/deezer/)%E2%80%8D",[850],"https://www.linkedin.com/company/deezer/)‍",[76105,76512,76514],{"id":76513},"career-page-5",[846,76515,73577],{"href":55292,"rel":76516},[850],[958,76518,76519,76522,76524,76526,76529,76532,76534],{},[961,76520,76521],{},"IT Support Apprentice m/f/d",[961,76523,75095],{},[961,76525,75101],{},[961,76527,76528],{},"Senior Machine Learning Engineer - Search Engine",[961,76530,76531],{},"Data Analyst Intern - Product & Content",[961,76533,75098],{},[961,76535,76536],{},"Data Analyst - Operational Performance Team (Apprenticeship)",[842,76538,76539],{},"Location(s): France",[842,76541,52316],{},[1074,76543,55009],{"id":55006},[76105,76545,76108],{"id":76546},"linkedin-profile-11",[842,76548,76237,76549,76237,76554,1410],{},[846,76550,76553],{"href":76551,"rel":76552},"https://www.linkedin.com/company/disco-ac/)%5B%E2%80%8D",[850],"https://www.linkedin.com/company/disco-ac/)[‍",[846,76555,76556],{"href":76556,"rel":76557},"https://apply.workable.com/disco/",[850],[76105,76559,76561],{"id":76560},"career-page-6",[846,76562,73577],{"href":76556,"rel":76563},[850],[958,76565,76566],{},[961,76567,76568],{},"Head of Engineering",[842,76570,76571],{},"Location(s): Australia",[842,76573,52316],{},[1074,76575,57251],{"id":57248},[76105,76577,76108],{"id":76578},"linkedin-profile-12",[842,76580,76349,76581],{},[846,76582,52316],{"href":70312,"rel":76583},[850],[76105,76585,76587],{"id":76586},"career-page-7",[846,76588,73577],{"href":70312,"rel":76589},[850],[958,76591,76592,76595,76598,76600,76602,76604,76606,76609,76612,76615,76617,76619,76622,76625,76628],{},[961,76593,76594],{},"Senior Imaging Software Engineer",[961,76596,76597],{},"Senior Technical Writer, Audio Content",[961,76599,70322],{},[961,76601,75101],{},[961,76603,70344],{},[961,76605,75135],{},[961,76607,76608],{},"Data Analyst Intern (Summer 2024)",[961,76610,76611],{},"Intern, Improving web portal (Summer 2024)",[961,76613,76614],{},"Head of Multimodal AI Research",[961,76616,54985],{},[961,76618,75101],{},[961,76620,76621],{},"Sr Distributed Research Engineer",[961,76623,76624],{},"Senior Platform Manager, Audio",[961,76626,76627],{},"Senior AI Audio Researcher",[961,76629,75140],{},[842,76631,76632],{},"Location(s): Poland, India, Sweden & U.S.",[842,76634,52316],{},[1074,76636,55153,76637],{"id":55150},[846,76638,52316],{"href":55162,"rel":76639},[850],[76105,76641,76108],{"id":76642},"linkedin-profile-13",[842,76644,76237,76645],{},[846,76646,76649],{"href":76647,"rel":76648},"https://www.linkedin.com/company/elevenlabsio/)%E2%80%8D",[850],"https://www.linkedin.com/company/elevenlabsio/)‍",[76105,76651,52316,76653],{"id":76652},"career-page-8",[846,76654,73577],{"href":55162,"rel":76655},[850],[958,76657,76658,76661],{},[961,76659,76660],{},"Front-End Engineer",[961,76662,75164],{},[842,76664,76665],{},"Location(s): Europe & U.S. (East Coast)",[842,76667,52316],{},[1074,76669,59250,76670],{"id":66676},[846,76671,52316],{"href":59253,"rel":76672},[850],[76105,76674,76676],{"id":76675},"linkedin-profile-14",[846,76677,73573],{"href":59253,"rel":76678},[850],[842,76680,76681],{},[846,76682,73577],{"href":75183,"rel":76683},[850],[958,76685,76686,76691,76695,76698,76700,76702,76704,76706,76708,76710],{},[961,76687,76688,76689],{},"Senior Backend Engineer - Digital Signal Processing",[996,76690,52316],{},[961,76692,75203,76693,52316],{},[996,76694,52316],{},[961,76696,76697],{},"Martech Engineer",[961,76699,75200],{},[961,76701,58997],{},[961,76703,54118],{},[961,76705,52376],{},[961,76707,55659],{},[961,76709,75189],{},[961,76711,75211],{},[842,76713,76714],{},"Location(s): Sweden",[842,76716,52316],{},[1074,76718,68946,76719],{"id":75216},[846,76720,52316],{"href":76721,"rel":76722},"https://www.fm.co/careers",[850],[76105,76724,76726],{"id":76725},"career-page-9",[846,76727,73577],{"href":76721,"rel":76728},[850],[958,76730,76731,76733],{},[961,76732,75230],{},[961,76734,76735],{},"Product Designer (UI/UX)",[842,76737,76738],{},"Location(s): U.S. & remote",[842,76740,52316],{},[1074,76742,12942,76743,76746],{"id":53015},[846,76744,52316],{"href":53026,"rel":76745},[850],[846,76747,52316],{"href":53026,"rel":76748},[850],[76105,76750,76752,4340],{"id":76751},"linkedin-profile-15",[846,76753,73573],{"href":53022,"rel":76754},[850],[842,76756,76757,76758,1410],{},"‍](",[846,76759,53026],{"href":53026,"rel":76760},[850],[76105,76762,76764],{"id":76763},"career-page-10",[846,76765,73577],{"href":53026,"rel":76766},[850],[958,76768,76769,76772],{},[961,76770,76771],{},"Client Integrations Manager",[961,76773,75249],{},[842,76775,76776],{},"Location(s): Netherlands & France",[842,76778,52316],{},[1074,76780,69371,76781],{"id":75254},[846,76782,52316],{"href":58773,"rel":76783},[850],[76105,76785,76787,4340],{"id":76786},"linkedin-profile-16",[846,76788,73573],{"href":58769,"rel":76789},[850],[842,76791,76757,76792,1410],{},[846,76793,58773],{"href":58773,"rel":76794},[850],[76105,76796,76798],{"id":76797},"career-page-11",[846,76799,73577],{"href":58773,"rel":76800},[850],[958,76802,76803],{},[961,76804,75269],{},[842,76806,76807],{},"Location(s): Ireland",[842,76809,52316],{},[1074,76811,73906,76812],{"id":73903},[846,76813,52316],{"href":73917,"rel":76814},[850],[76105,76816,76818,4340],{"id":76817},"linkedin-profile-17",[846,76819,73573],{"href":73913,"rel":76820},[850],[842,76822,76757,76823,1410],{},[846,76824,73917],{"href":73917,"rel":76825},[850],[76105,76827,76829],{"id":76828},"career-page-12",[846,76830,73577],{"href":73917,"rel":76831},[850],[958,76833,76834],{},[961,76835,69266],{},[842,76837,76838],{},"Location(s): U.S. & Brasil",[842,76840,52316],{},[1074,76842,57107,76843],{"id":57104},[846,76844,52316],{"href":76845,"rel":76846},"https://www.mu.se/careers/",[850],[76105,76848,76850,76853],{"id":76849},"_1",[846,76851,52316],{"href":57112,"rel":76852},[850],[846,76854,52316],{"href":76845,"rel":76855},[850],[76105,76857,76859],{"id":76858},"career-page-13",[846,76860,73577],{"href":76845,"rel":76861},[850],[958,76863,76864,76866,76868,76871,76873,76875,76877],{},[961,76865,75384],{},[961,76867,73043],{},[961,76869,76870],{},"C++ developer at Audacity",[961,76872,54985],{},[961,76874,53498],{},[961,76876,54614],{},[961,76878,57129],{},[842,76880,76881],{},"Location(s): Cyprus & remote",[842,76883,52316],{},[1074,76885,55848,76886],{"id":55845},[846,76887,52316],{"href":55857,"rel":76888},[850],[76105,76890,76892],{"id":76891},"career-page-14",[846,76893,73577],{"href":55857,"rel":76894},[850],[958,76896,76897,76900,76902],{},[961,76898,76899],{},"Senior Electronics Engineer",[961,76901,56329],{},[961,76903,75407],{},[842,76905,76906],{},"Location(s): Germany",[842,76908,52316],{},[1074,76910,76912],{"id":76911},"onerpm","OneRPM",[76105,76914,76108],{"id":76915},"linkedin-profile-18",[842,76917,76237,76918,76237,76923,1410],{},[846,76919,76922],{"href":76920,"rel":76921},"https://www.linkedin.com/company/onerpm/)%5B%E2%80%8D",[850],"https://www.linkedin.com/company/onerpm/)[‍",[846,76924,76925],{"href":76925,"rel":76926},"https://onerpm.rippling-ats.com/",[850],[76105,76928,76930],{"id":76929},"career-page-15",[846,76931,73577],{"href":76925,"rel":76932},[850],[958,76934,76935],{},[961,76936,76937],{},"Desenvolvedor Front-End Senior",[842,76939,76940],{},"Location(s): Brasil",[842,76942,52316],{},[1074,76944,55430,76945],{"id":55427},[846,76946,52316],{"href":70630,"rel":76947},[850],[76105,76949,52316,76951],{"id":76950},"career-page-16",[846,76952,73577],{"href":70630,"rel":76953},[850],[958,76955,76956],{},[961,76957,55776,76958],{},[996,76959,52316],{},[842,76961,76962],{},"Location(s): Lithuania",[842,76964,52316],{},[1074,76966,55515],{"id":55512},[76105,76968,73573,76970],{"id":76969},"linkedin-profile-19",[846,76971,52316],{"href":55520,"rel":76972},[850],[76105,76974,76976],{"id":76975},"career-page-17",[846,76977,73577],{"href":55524,"rel":76978},[850],[958,76980,76981],{},[961,76982,58943],{},[842,76984,76985],{},"Location(s): Ukraine",[842,76987,52316],{},[1074,76989,54413,76990,76993],{"id":54410},[846,76991,52316],{"href":75620,"rel":76992},[850],"‍[",[842,76995,76757,76996,1410],{},[846,76997,75620],{"href":75620,"rel":76998},[850],[76105,77000,77002],{"id":77001},"career-page-18",[846,77003,73577],{"href":75620,"rel":77004},[850],[958,77006,77007,77010,77013,77016,77019,77022,77025,77028,77031,77034,77037],{},[961,77008,77009],{},"Senior Staff Software Engineer – Embedded C/C++",[961,77011,77012],{},"Staff Data Engineer - Spark, Python, Scala",[961,77014,77015],{},"Staff Network Engineer - Broadcast",[961,77017,77018],{},"Director, Quality Engineering",[961,77020,77021],{},"Senior Software Engineer, Scala functional Programmer",[961,77023,77024],{},"Staff Product Designer - Programming & Tooling",[961,77026,77027],{},"Senior Data Engineer, Data Platform Infrastructure",[961,77029,77030],{},"Staff Engineer - Site Development",[961,77032,77033],{},"Staff Quality Engineer",[961,77035,77036],{},"Principal DevOps Engineer (Cloud)",[961,77038,70510],{},[842,77040,77041],{},"Location(s): U.S. & Romania",[842,77043,52316],{},[1074,77045,70650],{"id":70649},[842,77047,77048],{},[846,77049,77052],{"href":77050,"rel":77051},"https://sennheiser.referrals.selectminds.com/latest-jobs",[850],"\nCareer Page",[958,77054,77055,77058,77060,77062,77065,77067,77069,77071],{},[961,77056,77057],{},"Service Engineer",[961,77059,75562],{},[961,77061,75565],{},[961,77063,77064],{},"UI / Web UI / Front End / Back End Engineer",[961,77066,75568],{},[961,77068,54717],{},[961,77070,75573],{},[961,77072,75579],{},[842,77074,77075],{},"Location(s): Germany, Switzerland & Singapore",[842,77077,52316],{},[1074,77079,53200,77080],{"id":53197},[846,77081,52316],{"href":53205,"rel":77082},[850],[76105,77084,77086],{"id":77085},"linkedin-profile-20",[846,77087,73573],{"href":53205,"rel":77088},[850],[842,77090,77091],{},[846,77092,73577],{"href":53209,"rel":77093},[850],[958,77095,77096,77098],{},[961,77097,75670],{},[961,77099,58997],{},[842,77101,77102],{},"Location(s): Germany & U.K.",[842,77104,52316],{},[1074,77106,56227,77107],{"id":56224},[846,77108,52316],{"href":56236,"rel":77109},[850],[76105,77111,77113,77116],{"id":77112},"_2",[846,77114,52316],{"href":56232,"rel":77115},[850],[846,77117,52316],{"href":56236,"rel":77118},[850],[76105,77120,77122],{"id":77121},"career-page-19",[846,77123,73577],{"href":56236,"rel":77124},[850],[958,77126,77127],{},[961,77128,56245],{},[842,77130,76306],{},[842,77132,52316],{},[1074,77134,66830,77135],{"id":66829},[846,77136,52316],{"href":66839,"rel":77137},[850],[76105,77139,77141,4340],{"id":77140},"linkedin-profile-21",[846,77142,73573],{"href":66835,"rel":77143},[850],[842,77145,76757,77146,1410],{},[846,77147,66839],{"href":66839,"rel":77148},[850],[76105,77150,77152],{"id":77151},"career-page-20",[846,77153,73577],{"href":66839,"rel":77154},[850],[958,77156,77157,77159,77161,77163],{},[961,77158,53037],{},[961,77160,75763],{},[961,77162,70721],{},[961,77164,70730],{},[842,77166,77167],{},"Location(s): Germany, U.S. & U.K.",[842,77169,52316],{},[1074,77171,57880,77172],{"id":57877},[846,77173,52316],{"href":57891,"rel":77174},[850],[76105,77176,77178,4340],{"id":77177},"linkedin-profile-22",[846,77179,73573],{"href":57885,"rel":77180},[850],[842,77182,76757,77183,1410],{},[846,77184,57891],{"href":57891,"rel":77185},[850],[76105,77187,77189],{"id":77188},"career-page-21",[846,77190,73577],{"href":57891,"rel":77191},[850],[958,77193,77194,77196,77198,77201,77203,77205],{},[961,77195,75802],{},[961,77197,57901],{},[961,77199,77200],{},"Senior Software Engineer, License and Usage Data Management Teams",[961,77202,75794],{},[961,77204,75782],{},[961,77206,77207],{},"Technical Product Owner",[842,77209,76137],{},[842,77211,52316],{},[1074,77213,69704,77214],{"id":75807},[846,77215,52316],{"href":69713,"rel":77216},[850],[76105,77218,77220],{"id":77219},"career-page-22",[846,77221,73577],{"href":69713,"rel":77222},[850],[958,77224,77225,77227],{},[961,77226,59302],{},[961,77228,52782],{},[842,77230,76714],{},[842,77232,52316],{},[1074,77234,5070],{"id":12966},[76105,77236,77238],{"id":77237},"linkedin-profile-23",[846,77239,73573],{"href":66516,"rel":77240},[850],[842,77242,77243],{},[846,77244,73577],{"href":73272,"rel":77245},[850],[958,77247,77248,77250,77252,77254,77257],{},[961,77249,53093],{},[961,77251,73287],{},[961,77253,58997],{},[961,77255,77256],{},"Staff Backend Engineer",[961,77258,52653],{},[842,77260,77261],{},"Location(s): U.S. & U.K.",[842,77263,52316],{},[1074,77265,56962],{"id":56959},[76105,77267,77269],{"id":77268},"career-page-23",[846,77270,73577],{"href":74120,"rel":77271},[850],[958,77273,77274,77276,77278],{},[961,77275,53093],{},[961,77277,69479],{},[961,77279,52586],{},[842,77281,76137],{},[842,77283,52316],{},[1074,77285,74158,77286],{"id":74155},[846,77287,52316],{"href":77288,"rel":77289},"https://symphonicdist.bamboohr.com/careers/358",[850],[76105,77291,77293,4340],{"id":77292},"linkedin-profile-24",[846,77294,73573],{"href":54213,"rel":77295},[850],[842,77297,76757,77298,1410],{},[846,77299,54217],{"href":54217,"rel":77300},[850],[76105,77302,77304],{"id":77303},"career-page-24",[846,77305,73577],{"href":54217,"rel":77306},[850],[958,77308,77309],{},[961,77310,54226],{},[842,77312,77313],{},"Location(s): Chile, Colombia & Mexico",[842,77315,52316],{},[1074,77317,5037,77318],{"id":74172},[846,77319,52316],{"href":73390,"rel":77320},[850],[76105,77322,77324,4340],{"id":77323},"linkedin-profile-25",[846,77325,73573],{"href":73386,"rel":77326},[850],[842,77328,76757,77329,1410],{},[846,77330,73390],{"href":73390,"rel":77331},[850],[76105,77333,77335],{"id":77334},"career-page-25",[846,77336,73577],{"href":73390,"rel":77337},[850],[958,77339,77340,77342],{},[961,77341,58997],{},[961,77343,52623],{},[842,77345,77346],{},"Location(s): U.K. & Germany",[842,77348,52316],{},[1074,77350,59050],{"id":74188},[76105,77352,77354],{"id":77353},"career-page-26",[846,77355,73577],{"href":73374,"rel":77356},[850],[958,77358,77359,77361],{},[961,77360,75986],{},[961,77362,55776],{},[842,77364,76469],{},[842,77366,52316],{},[1074,77368,77370],{"id":77369},"tixy","TIXY",[76105,77372,76108],{"id":77373},"linkedin-profile-26",[842,77375,76237,77376,1410],{},[846,77377,77378],{"href":77378,"rel":77379},"https://www.linkedin.com/company/tixyapp/",[850],[76105,77381,77383],{"id":77382},"job-post-in-polish",[846,77384,77387],{"href":77385,"rel":77386},"https://www.linkedin.com/feed/update/urn:li:activity:7196097201201156097/?originalSubdomain=pl",[850],"Job Post (in Polish)",[958,77389,77390],{},[961,77391,52662],{},[842,77393,77394],{},"Location(s): Poland",[842,77396,52316],{},[1074,77398,77400,77401],{"id":77399},"vevo","VEVO",[846,77402,52316],{"href":77403,"rel":77404},"https://jobs.lever.co/vevo",[850],[76105,77406,77408,4340],{"id":77407},"linkedin-profile-27",[846,77409,73573],{"href":77410,"rel":77411},"https://www.linkedin.com/company/vevo/",[850],[842,77413,76757,77414,1410],{},[846,77415,77403],{"href":77403,"rel":77416},[850],[76105,77418,77420],{"id":77419},"career-page-27",[846,77421,73577],{"href":77403,"rel":77422},[850],[958,77424,77425],{},[961,77426,77427],{},"Manager, Data Analytics",[842,77429,76137],{},[842,77431,52316],{},[1074,77433,74253],{"id":74250},[76105,77435,77437],{"id":77436},"career-page-28",[846,77438,73577],{"href":77439,"rel":77440},"https://vydia.com/about/careers/",[850],[958,77442,77443,77446,77448],{},[961,77444,77445],{},"Director of DevOps",[961,77447,57594],{},[961,77449,73437],{},[842,77451,76137],{},[842,77453,52316],{},[1074,77455,56304,77456],{"id":56301},[846,77457,52316],{"href":70903,"rel":77458},[850],[76105,77460,77462,4340],{"id":77461},"linkedin-profile-28",[846,77463,73573],{"href":56309,"rel":77464},[850],[842,77466,76757,77467,1410],{},[846,77468,70903],{"href":70903,"rel":77469},[850],[76105,77471,77473],{"id":77472},"career-page-29",[846,77474,73577],{"href":70903,"rel":77475},[850],[958,77477,77478],{},[961,77479,57129],{},[842,77481,76498],{},[842,77483,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":77485},[77486,77488,77490,77492,77493,77494,77496,77497,77498,77499,77500,77501,77502,77503,77504,77506,77508,77510,77512,77514,77516,77518,77520,77521,77523,77524,77526,77527,77529,77531,77533,77535,77537,77538,77539,77541,77543,77544,77545,77547,77548],{"id":54550,"depth":1112,"text":77487},"AEG‍",{"id":66150,"depth":1112,"text":77489},"Amazon Music‍",{"id":54961,"depth":1112,"text":77491},"Artlist‍",{"id":55669,"depth":1112,"text":55672},{"id":53134,"depth":1112,"text":53137},{"id":74883,"depth":1112,"text":77495},"BandLab Technologies‍",{"id":55206,"depth":1112,"text":55209},{"id":74914,"depth":1112,"text":69205},{"id":56366,"depth":1112,"text":56369},{"id":53627,"depth":1112,"text":53630},{"id":75049,"depth":1112,"text":75050},{"id":55281,"depth":1112,"text":40953},{"id":55006,"depth":1112,"text":55009},{"id":57248,"depth":1112,"text":57251},{"id":55150,"depth":1112,"text":77505},"ElevenLabs‍",{"id":66676,"depth":1112,"text":77507},"Epidemic Sound‍",{"id":75216,"depth":1112,"text":77509},"FM‍",{"id":53015,"depth":1112,"text":77511},"FUGA‍‍",{"id":75254,"depth":1112,"text":77513},"Kobalt Music‍",{"id":73903,"depth":1112,"text":77515},"Music.AI‍",{"id":57104,"depth":1112,"text":77517},"Muse Group‍",{"id":55845,"depth":1112,"text":77519},"Native Instruments‍",{"id":76911,"depth":1112,"text":76912},{"id":55427,"depth":1112,"text":77522},"Podimo‍",{"id":55512,"depth":1112,"text":55515},{"id":54410,"depth":1112,"text":77525},"SiriusXM‍‍[",{"id":70649,"depth":1112,"text":70650},{"id":53197,"depth":1112,"text":77528},"Songtradr‍",{"id":56224,"depth":1112,"text":77530},"SonoSuite‍",{"id":66829,"depth":1112,"text":77532},"SoundCloud‍",{"id":57877,"depth":1112,"text":77534},"SoundExchange‍",{"id":75807,"depth":1112,"text":77536},"Soundtrack Your Brand‍",{"id":12966,"depth":1112,"text":5070},{"id":56959,"depth":1112,"text":56962},{"id":74155,"depth":1112,"text":77540},"Symphonic Distribution‍",{"id":74172,"depth":1112,"text":77542},"Tidal‍",{"id":74188,"depth":1112,"text":59050},{"id":77369,"depth":1112,"text":77370},{"id":77399,"depth":1112,"text":77546},"VEVO‍",{"id":74250,"depth":1112,"text":74253},{"id":56301,"depth":1112,"text":77549},"Yoto‍","2024-05-15T00:00:00.000Z","Curated list of tech job openings in the music industry for May 2024. Explore roles at music startups, labels, streaming platforms, and more.",{"src":77553},"/images/blog/musictechlab_blog_music-industry-tech-openings-may-2024-update.webp",{},{"title":245,"description":77551},[5678,5523],"3cR4nYYJaBsXD8entj3XUt5a-bSkEA9lD_mYouaXFLQ",{"id":77559,"title":209,"authors":77560,"badge":723,"body":77563,"category":5678,"client":723,"date":78351,"description":78352,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":78353,"keyTakeaways":723,"meta":78355,"navigation":738,"path":210,"seo":78356,"status":723,"stem":211,"tags":78357,"teaser":723,"__hash__":78358},"posts/blog/newsletter/music-industry-tech-openings-april-2024-update.md",[77561],{"name":50093,"to":50094,"avatar":77562},{"src":50096},{"type":725,"value":77564,"toc":78316},[77565,77569,77574,77579,77602,77606,77611,77616,77643,77647,77652,77657,77677,77681,77686,77712,77716,77721,77728,77730,77735,77740,77757,77763,77769,77779,77783,77788,77793,77797,77802,77813,77817,77822,77829,77833,77838,77844,77848,77853,77858,77888,77892,77897,77901,77905,77910,77919,77923,77928,77932,77937,77942,77946,77950,77955,77971,77975,77980,77991,77995,78000,78010,78016,78022,78027,78031,78036,78075,78079,78084,78116,78120,78125,78129,78131,78136,78141,78150,78154,78159,78166,78170,78175,78184,78188,78193,78217,78221,78226,78230,78234,78239,78243,78247,78252,78262,78268,78274,78279,78283,78288,78292,78296,78301,78314],[1074,77566,77567],{"id":54550},[996,77568,54553],{},[842,77570,77571],{},[846,77572,73573],{"href":54558,"rel":77573},[850],[842,77575,77576],{},[846,77577,73577],{"href":74658,"rel":77578},[850],[958,77580,77581,77584,77587,77589,77591,77594,77597,77600],{},[961,77582,77583],{},"Application Support Analyst",[961,77585,77586],{},"Business Applications Analyst",[961,77588,74674],{},[961,77590,54614],{},[961,77592,77593],{},"DevOps Manager",[961,77595,77596],{},"Front-End Web Developer",[961,77598,77599],{},"Senior Full-Stack Engineer",[961,77601,76119],{},[1074,77603,77604],{"id":66150},[996,77605,5059],{},[842,77607,77608],{},[846,77609,76377],{"href":66155,"rel":77610},[850],[842,77612,77613],{},[846,77614,73577],{"href":74697,"rel":77615},[850],[958,77617,77618,77620,77622,77624,77627,77630,77632,77635,77638,77641],{},[961,77619,76165],{},[961,77621,53093],{},[961,77623,76168],{},[961,77625,77626],{},"Senior Software Development Engineer III (Scala/Ruby)",[961,77628,77629],{},"Senior Systems Development Engineer",[961,77631,56600],{},[961,77633,77634],{},"Sr. Product Manager",[961,77636,77637],{},"Sr. Product Manager - Technical",[961,77639,77640],{},"Sr. Technical Program Manager",[961,77642,74712],{},[1074,77644,77645],{"id":54961},[996,77646,54964],{},[842,77648,77649],{},[846,77650,76377],{"href":70017,"rel":77651},[850],[842,77653,77654],{},[846,77655,73577],{"href":70021,"rel":77656},[850],[958,77658,77659,77661,77663,77665,77667,77669,77672,77674],{},[961,77660,52659],{},[961,77662,68978],{},[961,77664,74782],{},[961,77666,76218],{},[961,77668,58805],{},[961,77670,77671],{},"Product Manager- Core",[961,77673,54990],{},[961,77675,77676],{},"Senior Web Designer",[1074,77678,77679],{"id":55669},[996,77680,55672],{},[842,77682,77683],{},[846,77684,73577],{"href":55680,"rel":77685},[850],[958,77687,77688,77691,77694,77696,77699,77702,77705,77708,77710],{},[961,77689,77690],{},"Salesforce Developer",[961,77692,77693],{},"Technical Support Engineer - Shared Storage",[961,77695,69101],{},[961,77697,77698],{},"Technical Support Engineer (Graphics)",[961,77700,77701],{},"FastServe Technical Support Engineer",[961,77703,77704],{},"Integration Architect",[961,77706,77707],{},"Technical Support Engineer - Audio",[961,77709,52470],{},[961,77711,74864],{},[1074,77713,77714],{"id":53134},[996,77715,53137],{},[842,77717,77718],{},[846,77719,73577],{"href":53146,"rel":77720},[850],[958,77722,77723,77726],{},[961,77724,77725],{},"IT Operations Engineer",[961,77727,70242],{},[1074,77729,58822],{"id":74883},[842,77731,77732],{},[846,77733,73573],{"href":54503,"rel":77734},[850],[842,77736,77737],{},[846,77738,73577],{"href":54507,"rel":77739},[850],[958,77741,77742,77745,77747,77749,77752,77754],{},[961,77743,77744],{},"Senior Backend Engineer, Opportunities Team",[961,77746,70153],{},[961,77748,70159],{},[961,77750,77751],{},"Senior Backend Engineer, BandLab For Education",[961,77753,74905],{},[961,77755,77756],{},"iOS Infrastructure Engineer",[1074,77758,77760],{"id":77759},"baton",[996,77761,77762],{},"Baton",[842,77764,77765],{},[846,77766,73577],{"href":77767,"rel":77768},"https://batonmedia.notion.site/Jobs-Baton-18cb36794bc7402f8589212a3b1e9234",[850],[958,77770,77771,77774,77777],{},[961,77772,77773],{},"Fulstack Engineer",[961,77775,77776],{},"Mobile Engineer",[961,77778,53037],{},[1074,77780,77781],{"id":55206},[996,77782,55209],{},[842,77784,77785],{},[846,77786,73577],{"href":76352,"rel":77787},[850],[958,77789,77790],{},[961,77791,77792],{},"Back-end Engineer (Contractor)",[1074,77794,77795],{"id":56366},[996,77796,56369],{},[842,77798,77799],{},[846,77800,73577],{"href":56378,"rel":77801},[850],[958,77803,77804,77807,77809,77811],{},[961,77805,77806],{},"Application manager JIRA",[961,77808,52653],{},[961,77810,52745],{},[961,77812,52504],{},[1074,77814,77815],{"id":53627},[996,77816,53630],{},[842,77818,77819],{},[846,77820,73577],{"href":53639,"rel":77821},[850],[958,77823,77824,77827],{},[961,77825,77826],{},"Back-end / DevOps Engineer",[961,77828,53655],{},[1074,77830,77831],{"id":55150},[996,77832,55153],{},[842,77834,77835],{},[846,77836,73577],{"href":55162,"rel":77837},[850],[958,77839,77840,77842],{},[961,77841,76660],{},[961,77843,75164],{},[1074,77845,77846],{"id":66676},[996,77847,59250],{},[842,77849,77850],{},[846,77851,73573],{"href":59253,"rel":77852},[850],[842,77854,77855],{},[846,77856,73577],{"href":75183,"rel":77857},[850],[958,77859,77860,77862,77865,77867,77870,77872,77874,77876,77878,77880,77882,77885],{},[961,77861,53093],{},[961,77863,77864],{},"Identity and Access Management (IAM) Engineer",[961,77866,77690],{},[961,77868,77869],{},"Engineering Manager - Analytics Engineering",[961,77871,55027],{},[961,77873,70281],{},[961,77875,58997],{},[961,77877,53398],{},[961,77879,54570],{},[961,77881,54118],{},[961,77883,77884],{},"VP of Engineering - Core Services",[961,77886,77887],{},"Senior Analyst, Commercial Analytics",[1074,77889,77890],{"id":75216},[996,77891,68946],{},[842,77893,77894],{},[846,77895,73577],{"href":76721,"rel":77896},[850],[958,77898,77899],{},[961,77900,75230],{},[1074,77902,77903],{"id":53015},[996,77904,12942],{},[842,77906,77907],{},[846,77908,73577],{"href":53026,"rel":77909},[850],[958,77911,77912,77915,77917],{},[961,77913,77914],{},"Mechanical Royalties Administration Consultant",[961,77916,75249],{},[961,77918,52376],{},[1074,77920,77921],{"id":75254},[996,77922,69371],{},[842,77924,77925],{},[846,77926,73577],{"href":58773,"rel":77927},[850],[958,77929,77930],{},[961,77931,69673],{},[1074,77933,77934],{"id":73903},[996,77935,77936],{},"MUSIC.AI",[842,77938,77939],{},[846,77940,73577],{"href":73917,"rel":77941},[850],[958,77943,77944],{},[961,77945,69266],{},[1074,77947,77948],{"id":57104},[996,77949,57107],{},[842,77951,77952],{},[846,77953,73577],{"href":76845,"rel":77954},[850],[958,77956,77957,77960,77963],{},[961,77958,77959],{},"Analytics Engineer at Muse",[961,77961,77962],{},"C++ Developer at Audacity",[961,77964,77965,77966],{},"Senior Frontend Engineer at ",[846,77967,77970],{"href":77968,"rel":77969},"https://Audio.com",[850],"Audio.com",[1074,77972,77973],{"id":55845},[996,77974,55848],{},[842,77976,77977],{},[846,77978,73577],{"href":55857,"rel":77979},[850],[958,77981,77982,77985,77987,77989],{},[961,77983,77984],{},"Senior Director of Product - Hardware & DJ",[961,77986,76899],{},[961,77988,56329],{},[961,77990,75407],{},[1074,77992,77993],{"id":55427},[996,77994,55430],{},[842,77996,77997],{},[846,77998,73577],{"href":70630,"rel":77999},[850],[958,78001,78002,78004,78006,78008],{},[961,78003,58205],{},[961,78005,52439],{},[961,78007,55776],{},[961,78009,55664],{},[1074,78011,78013],{"id":78012},"roex",[996,78014,78015],{},"RoEx",[842,78017,78018],{},[846,78019,73577],{"href":78020,"rel":78021},"https://www.linkedin.com/company/roexaudio/jobs/",[850],[958,78023,78024],{},[961,78025,78026],{},"Research Engineer",[1074,78028,78029],{"id":54410},[996,78030,54413],{},[842,78032,78033],{},[846,78034,73577],{"href":75620,"rel":78035},[850],[958,78037,78038,78041,78044,78047,78049,78052,78054,78057,78060,78063,78066,78069,78072],{},[961,78039,78040],{},"Lead Analyst, Governance, Risk, and Compliance",[961,78042,78043],{},"Staff Software Engineer, C++",[961,78045,78046],{},"Principal Engineer – OEM Software Support",[961,78048,52504],{},[961,78050,78051],{},"UX/UI Designer - Agency Temp",[961,78053,53398],{},[961,78055,78056],{},"Summer Intern, Software Engineering",[961,78058,78059],{},"Engineer III, DevSecOps",[961,78061,78062],{},"Senior Software Engineer - Scala functional programming",[961,78064,78065],{},"Senior Manager, iOS Software Engineering",[961,78067,78068],{},"Senior Staff Software Engineer (contractor)",[961,78070,78071],{},"Senior iOS Developer - SDK (contractor)",[961,78073,78074],{},"Junior Designer, Graphics and Video, Digital Channels - The Howard Stern Show",[1074,78076,78077],{"id":70649},[996,78078,70650],{},[842,78080,78081],{},[846,78082,73577],{"href":77050,"rel":78083},[850],[958,78085,78086,78089,78092,78095,78097,78099,78101,78104,78106,78108,78110,78113],{},[961,78087,78088],{},"Director of Modules and Platform",[961,78090,78091],{},"Elektroniker für Geräte und Systeme",[961,78093,78094],{},"Elektroniker für Geräte und Systeme mit Schwerpunkt Akustik",[961,78096,54717],{},[961,78098,75573],{},[961,78100,75576],{},[961,78102,78103],{},"Product Manager Software",[961,78105,75585],{},[961,78107,75582],{},[961,78109,75568],{},[961,78111,78112],{},"Techniker Produktionsunterstützung",[961,78114,78115],{},"UX Strategy Manager",[1074,78117,78118],{"id":56277},[996,78119,56280],{},[842,78121,78122],{},[846,78123,73577],{"href":56289,"rel":78124},[850],[958,78126,78127],{},[961,78128,53282],{},[1074,78130,53200],{"id":53197},[842,78132,78133],{},[846,78134,73573],{"href":53205,"rel":78135},[850],[842,78137,78138],{},[846,78139,73577],{"href":53209,"rel":78140},[850],[958,78142,78143,78145,78148],{},[961,78144,75670],{},[961,78146,78147],{},"Senior Designer",[961,78149,58997],{},[1074,78151,78152],{"id":56224},[996,78153,56227],{},[842,78155,78156],{},[846,78157,73577],{"href":56236,"rel":78158},[850],[958,78160,78161,78163],{},[961,78162,53093],{},[961,78164,78165],{},"Team Lead",[1074,78167,78168],{"id":66829},[996,78169,66830],{},[842,78171,78172],{},[846,78173,73577],{"href":66839,"rel":78174},[850],[958,78176,78177,78179,78181],{},[961,78178,53037],{},[961,78180,70721],{},[961,78182,78183],{},"Senior Cloud Platform Engineer",[1074,78185,78186],{"id":57877},[996,78187,57880],{},[842,78189,78190],{},[846,78191,73577],{"href":57891,"rel":78192},[850],[958,78194,78195,78197,78200,78203,78205,78207,78210,78212,78214],{},[961,78196,75797],{},[961,78198,78199],{},"Quality Assurance Lead",[961,78201,78202],{},"Lead Technical Product Owner",[961,78204,77207],{},[961,78206,57904],{},[961,78208,78209],{},"Senior Software Engineer, Python – License and Usage Data Management Teams",[961,78211,75788],{},[961,78213,57901],{},[961,78215,78216],{},"Lead Software Engineer, Rights and Repertoire Teams",[1074,78218,78219],{"id":75807},[996,78220,69704],{},[842,78222,78223],{},[846,78224,73577],{"href":69713,"rel":78225},[850],[958,78227,78228],{},[961,78229,55027],{},[1074,78231,78232],{"id":74155},[996,78233,74158],{},[842,78235,78236],{},[846,78237,73577],{"href":77288,"rel":78238},[850],[958,78240,78241],{},[961,78242,54226],{},[1074,78244,78245],{"id":74172},[996,78246,5037],{},[842,78248,78249],{},[846,78250,73577],{"href":73390,"rel":78251},[850],[958,78253,78254,78257,78259],{},[961,78255,78256],{},"Machine Learning Engineer, Personalization Team",[961,78258,58997],{},[961,78260,78261],{},"Technical Lead - Software Engineer, Backend",[1074,78263,78265],{"id":78264},"tracklib",[996,78266,78267],{},"Tracklib",[842,78269,78270],{},[846,78271,73577],{"href":78272,"rel":78273},"https://careers.tracklib.com/jobs",[850],[958,78275,78276],{},[961,78277,78278],{},"Automation and Process Lead",[1074,78280,78281],{"id":77399},[996,78282,77400],{},[842,78284,78285],{},[846,78286,73577],{"href":77403,"rel":78287},[850],[958,78289,78290],{},[961,78291,77427],{},[1074,78293,78294],{"id":56301},[996,78295,56304],{},[842,78297,78298],{},[846,78299,73577],{"href":70903,"rel":78300},[850],[958,78302,78303,78306,78309,78311],{},[961,78304,78305],{},"Manual QA Engineer (Embedded Software)",[961,78307,78308],{},"Senior Hardware Engineer",[961,78310,52504],{},[961,78312,78313],{},"Senior Software Engineer (Purchasing)",[842,78315,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":78317},[78318,78319,78320,78321,78322,78323,78324,78325,78326,78327,78328,78329,78330,78331,78332,78333,78334,78335,78336,78337,78338,78339,78340,78341,78342,78343,78344,78345,78346,78347,78348,78349,78350],{"id":54550,"depth":1112,"text":54553},{"id":66150,"depth":1112,"text":5059},{"id":54961,"depth":1112,"text":54964},{"id":55669,"depth":1112,"text":55672},{"id":53134,"depth":1112,"text":53137},{"id":74883,"depth":1112,"text":58822},{"id":77759,"depth":1112,"text":77762},{"id":55206,"depth":1112,"text":55209},{"id":56366,"depth":1112,"text":56369},{"id":53627,"depth":1112,"text":53630},{"id":55150,"depth":1112,"text":55153},{"id":66676,"depth":1112,"text":59250},{"id":75216,"depth":1112,"text":68946},{"id":53015,"depth":1112,"text":12942},{"id":75254,"depth":1112,"text":69371},{"id":73903,"depth":1112,"text":77936},{"id":57104,"depth":1112,"text":57107},{"id":55845,"depth":1112,"text":55848},{"id":55427,"depth":1112,"text":55430},{"id":78012,"depth":1112,"text":78015},{"id":54410,"depth":1112,"text":54413},{"id":70649,"depth":1112,"text":70650},{"id":56277,"depth":1112,"text":56280},{"id":53197,"depth":1112,"text":53200},{"id":56224,"depth":1112,"text":56227},{"id":66829,"depth":1112,"text":66830},{"id":57877,"depth":1112,"text":57880},{"id":75807,"depth":1112,"text":69704},{"id":74155,"depth":1112,"text":74158},{"id":74172,"depth":1112,"text":5037},{"id":78264,"depth":1112,"text":78267},{"id":77399,"depth":1112,"text":77400},{"id":56301,"depth":1112,"text":56304},"2024-04-11T00:00:00.000Z","Curated list of tech job openings in the music industry for April 2024. Explore roles at music startups, labels, streaming platforms, and more.",{"src":78354},"/images/blog/musictechlab_blog_music-industry-tech-openings-april-2024-update.webp",{},{"title":209,"description":78352},[5678,5523],"sgftLVfqDtOlHXhRafG_0NpQ9lqzaGCaBx05EiIHV7E",{"id":78360,"title":622,"authors":78361,"badge":723,"body":78364,"category":756,"client":723,"date":78646,"description":78647,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":78648,"keyTakeaways":78650,"meta":78658,"navigation":738,"path":623,"seo":78659,"status":723,"stem":624,"tags":78660,"teaser":723,"__hash__":78661},"posts/blog/software-development/technical-job-opportunities-in-the-music-industry.md",[78362],{"name":50093,"to":50094,"avatar":78363},{"src":50096},{"type":725,"value":78365,"toc":78644},[78366,78368,78371,78374,78377,78381,78402,78407,78423,78427,78466,78470,78488,78492,78516,78521,78536,78541,78556,78561,78576,78580,78599,78603,78642],[842,78367,40867],{},[842,78369,78370],{},"Our mission expanded beyond just building music tech solutions. We also want to attract talented developers, data engineers, UX/UI designers, and other technical professionals to the music sector.",[842,78372,78373],{},"While there are job boards catering to non-technical roles, we'll be focusing on promoting technical job opportunities. Our goal is to bring in exceptional talent who can contribute to groundbreaking projects and innovations in music.",[842,78375,78376],{},"Here are some technical job openings with details (checked on 19.03.2024). Feel free to apply if you meet the requirements:",[842,78378,78379],{},[996,78380,77762],{},[958,78382,78383,78389,78395,78397,78399],{},[961,78384,78385],{},[846,78386,5669],{"href":78387,"rel":78388},"https://www.linkedin.com/company/baton-media-inc/",[850],[961,78390,78391],{},[846,78392,78394],{"href":77767,"rel":78393},[850],"Jobs",[961,78396,77776],{},[961,78398,53037],{},[961,78400,78401],{},"Fulstack Engineer\n‍",[842,78403,78404],{},[996,78405,78406],{},"Beatstars",[958,78408,78409,78414,78420],{},[961,78410,78411],{},[846,78412,5669],{"href":55214,"rel":78413},[850],[961,78415,78416],{},[846,78417,78419],{"href":76352,"rel":78418},[850],"Job",[961,78421,78422],{},"Back-end Engineer (Contractor) (LATAM, Remote)\n‍",[842,78424,78425],{},[996,78426,56369],{},[958,78428,78429,78434,78439,78442,78445,78448,78451,78454,78457,78460,78463],{},[961,78430,78431],{},[846,78432,5669],{"href":56374,"rel":78433},[850],[961,78435,78436],{},[846,78437,78394],{"href":56378,"rel":78438},[850],[961,78440,78441],{},"Senior Software Engineer (France, Paris)",[961,78443,78444],{},"Developpeur Frontend React JS (France, Paris)",[961,78446,78447],{},"Workplace Engineer (France, Paris)",[961,78449,78450],{},"Software Engineer PHP (France, Paris)",[961,78452,78453],{},"Senior Software Developer (France, Paris)",[961,78455,78456],{},"QA Engineer (France, Paris)",[961,78458,78459],{},"Scrum Master Junior (France, Paris)",[961,78461,78462],{},"Software Engineer API REST PHP Cloud (France, Paris)",[961,78464,78465],{},"Senior Data Engineer (France, Paris)\n‍",[842,78467,78468],{},[996,78469,12942],{},[958,78471,78472,78477,78482,78485],{},[961,78473,78474],{},[846,78475,5669],{"href":53022,"rel":78476},[850],[961,78478,78479],{},[846,78480,78394],{"href":53026,"rel":78481},[850],[961,78483,78484],{},"Full Stack Developer (United Kingdom)",[961,78486,78487],{},"Ruby Engineer (Netherlands)\n‍",[842,78489,78490],{},[996,78491,57107],{},[958,78493,78494,78499,78504,78507,78510,78513],{},[961,78495,78496],{},[846,78497,5669],{"href":57112,"rel":78498},[850],[961,78500,78501],{},[846,78502,52302],{"href":76845,"rel":78503},[850],[961,78505,78506],{},"Data Analyst (Cyprus or remote)",[961,78508,78509],{},"Analytics Engineer (Cyprus or remote)",[961,78511,78512],{},"C++ developer (Cyprus or remote)",[961,78514,78515],{},"Senior Frontend Engineer (Cyprus or remote)\n‍",[842,78517,78518],{},[996,78519,78520],{},"FM LLC",[958,78522,78523,78528,78533],{},[961,78524,78525],{},[846,78526,5669],{"href":68949,"rel":78527},[850],[961,78529,78530],{},[846,78531,52302],{"href":76721,"rel":78532},[850],[961,78534,78535],{},"Senior Software Engineer, Front End (Remote)\n‍",[842,78537,78538],{},[996,78539,78540],{},"Pixelynx",[958,78542,78543,78550,78553],{},[961,78544,78545],{},[846,78546,78549],{"href":78547,"rel":78548},"https://apply.workable.com/pixelynx/",[850],"Apply",[961,78551,78552],{},"Front-end Web Developer (Canada, Toronto or remote)",[961,78554,78555],{},"Backend and DevOps Engineer (Germany, Berlin)\n‍",[842,78557,78558],{},[996,78559,78560],{},"BMAT",[958,78562,78563,78568,78573],{},[961,78564,78565],{},[846,78566,5669],{"href":53142,"rel":78567},[850],[961,78569,78570],{},[846,78571,52302],{"href":53146,"rel":78572},[850],[961,78574,78575],{},"Software Python Engineer (Spain, Barcelona or Remote)\n‍",[842,78577,78578],{},[996,78579,68965],{},[958,78581,78582,78587,78593,78596],{},[961,78583,78584],{},[846,78585,5669],{"href":68968,"rel":78586},[850],[961,78588,78589],{},[846,78590,52302],{"href":78591,"rel":78592},"https://soundful.com/en-us/careers/",[850],[961,78594,78595],{},"Senior Software Engineer (United States)",[961,78597,78598],{},"Data Scientist (United States)\n‍",[842,78600,78601],{},[996,78602,54468],{},[958,78604,78605,78610,78615,78618,78621,78624,78627,78630,78633,78636,78639],{},[961,78606,78607],{},[846,78608,5669],{"href":54473,"rel":78609},[850],[961,78611,78612],{},[846,78613,52302],{"href":54477,"rel":78614},[850],[961,78616,78617],{},"Lead Data Scientist (United States, New York)",[961,78619,78620],{},"Machine Learning Engineer (United States, New York)",[961,78622,78623],{},"Machine Learning Engineer (France or Remote)",[961,78625,78626],{},"Senior Data Scientist (United States, New York)",[961,78628,78629],{},"Senior Software Engineer (UI) (United States, Los Angeles)",[961,78631,78632],{},"Software Engineer (United States, Los Angeles or remote)",[961,78634,78635],{},"Senior Technical Product Manager (United States, Los Angeles)",[961,78637,78638],{},"Senior UI/UX Designer (United States, Los Angeles)",[961,78640,78641],{},"Senior UI Designer (United States, Los Angeles)",[842,78643,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":78645},[],"2024-03-19T00:00:00.000Z","When we decided to go all-in on the music industry, we knew from the start, that Bravelab team's support alone wouldn't be enough to drive the industry forward.",{"src":78649},"/images/blog/musictechlab_blog_technical-job-opportunities-in-the-music-industry.webp",{"enabled":738,"items":78651},[78652,78654,78656],{"text":78653,"icon":9547},"10+ music industry companies had open technical roles in March 2024 across 6+ countries.",{"text":78655,"icon":5365},"Roles span frontend, backend, data science, ML engineering, and UX/UI design.",{"text":78657,"icon":1067},"Companies like Believe, Luminate, and Muse Group actively recruit remote technical talent.",{},{"title":622,"description":78647},[5678,5523],"RJTkfe8xW9nYaMM9lpiowvLv_RiujasJOE-taIu-GgM",{"id":78663,"title":58,"authors":78664,"badge":78667,"body":78668,"category":4990,"client":78888,"date":78890,"description":78891,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":78892,"keyTakeaways":78894,"meta":78902,"navigation":738,"path":59,"seo":78903,"status":723,"stem":60,"tags":78904,"teaser":723,"__hash__":78905},"posts/blog/case-study/turn-fans-into-superfans-roadie-co.md",[78665],{"name":834,"to":720,"avatar":78666},{"src":722},{"label":5,"color":50099},{"type":725,"value":78669,"toc":78877},[78670,78673,78675,78679,78682,78697,78700,78706,78708,78712,78715,78725,78730,78732,78736,78739,78754,78756,78760,78776,78778,78782,78802,78804,78808,78815,78817,78821,78824,78852,78857,78859,78863,78866,78868,78870],[842,78671,78672],{},"Independent artists often face myriad challenges in the vast and ever-evolving music industry landscape. From establishing a solid online presence to effectively managing fanbases, the journey to success can seem daunting. However, with the emergence of innovative platforms like Roadie.co, artists can now access tools and resources to help them confidently navigate this complex terrain.",[4937,78674],{},[863,78676,78678],{"id":78677},"navigating-the-digital-stage","Navigating the Digital Stage",[842,78680,78681],{},"One of the most crucial aspects of an artist's career is their online presence. In today's digital age, having a professional website is essential for showcasing music and live shows.",[1045,78683,78685,78689,78693],{"className":78684},[1048,1049,1050,1051,1052],[1054,78686],{"description":78687,"icon":1067,"title":78688},"Create attractive, customizable websites that reflect your unique style and sound.","Website Builder",[1054,78690],{"description":78691,"icon":65185,"title":78692},"Add and schedule tour dates through an intuitive admin interface.","Tour Management",[1054,78694],{"description":78695,"icon":1062,"title":78696},"Link your own domain for a professional online presence.","Custom Domains",[842,78698,78699],{},"Roadie.co simplifies the process of adding and scheduling new tour dates and releases — no more tedious tasks or complicated procedures, allowing artists to focus on what they do best: creating music.",[842,78701,78702],{},[1027,78703],{"alt":78704,"src":78705},"Roadie.co partnership","/images/blog/musictechlab_blog_roadie-partnership.webp",[4937,78707],{},[863,78709,78711],{"id":78710},"data-driven-release-management","Data-Driven Release Management",[842,78713,78714],{},"When it comes to releasing new music, strategic planning is critical. Each track, EP, or album release deserves its dedicated page, and that's precisely what Roadie.co provides.",[1045,78716,78718,78722],{"className":78717},[1048,1049,1765,1051,1052],[1054,78719],{"description":78720,"icon":11486,"title":78721},"Create personalized pages for every track, EP, or album — easy to share with fans.","Release Pages",[1054,78723],{"description":78724,"icon":3844,"title":16765},"Gain insights into fanbase behaviour — website visits, social media engagement, and more.",[1032,78726,78727],{},[842,78728,78729],{},"What sets Roadie.co apart is its commitment to data-driven decision-making. Artists can make informed decisions that drive success and maximize their impact.",[4937,78731],{},[863,78733,78735],{"id":78734},"fandom","Fandom",[842,78737,78738],{},"Building a loyal fanbase is essential for long-term success in the music industry. While social media platforms are valuable for reaching fans, they can be fickle and ever-changing.",[1045,78740,78742,78746,78750],{"className":78741},[1048,1049,1050,1051,1052],[1054,78743],{"description":78744,"icon":52136,"title":78745},"Build one-on-one connections with fans and convert show attendees into subscribers.","Mailing Lists",[1054,78747],{"description":78748,"icon":3850,"title":78749},"Craft professional newsletters with easy-to-use templates.","Email Templates",[1054,78751],{"description":78752,"icon":5512,"title":78753},"Cultivate a dedicated fanbase that stands the test of time.","Superfan Cultivation",[4937,78755],{},[863,78757,78759],{"id":78758},"mission-vision-and-values","Mission, Vision and Values",[1045,78761,78763,78767,78771],{"className":78762},[1048,1049,1050,1051,1052],[1054,78764],{"description":78765,"icon":3915,"title":78766},"Clear, honest approach to how the platform works and what it costs.","Transparency",[1054,78768],{"description":78769,"icon":2895,"title":78770},"Artists own their data. Cancel anytime and take your data with you.","Data Ownership",[1054,78772],{"description":78773,"icon":78774,"title":78775},"No lock-in. Transfer data and cancel subscriptions at any time.","i-lucide-move","Flexibility",[4937,78777],{},[863,78779,78781],{"id":78780},"the-future","The Future",[1045,78783,78785,78790,78794,78798],{"className":78784},[1048,1049,1765,1051,1052],[1054,78786],{"description":78787,"icon":78788,"title":78789},"Dedicated pages for press materials and media kits.","i-lucide-newspaper","Press Release Pages",[1054,78791],{"description":78792,"icon":7555,"title":78793},"Track and showcase career milestones and achievements.","Career Highlights",[1054,78795],{"description":78796,"icon":66013,"title":78797},"Artists can access new tools and features before anyone else.","Early Access",[1054,78799],{"description":78800,"icon":37696,"title":78801},"The platform evolves to meet the changing needs of artists.","Constant Evolution",[4937,78803],{},[863,78805,78807],{"id":78806},"the-maker","The Maker",[842,78809,78810,78811,78814],{},"Behind Roadie.co is ",[996,78812,78813],{},"Joep",", an artist manager and founder passionate about supporting independent artists. Having witnessed firsthand the struggles faced by emerging talent, Joep set out to create a platform that would level the playing field and give artists the tools they need to succeed.",[4937,78816],{},[863,78818,78820],{"id":78819},"the-tech-challenge","The Tech Challenge",[842,78822,78823],{},"MusicTech Lab's cooperation with Roadie started because building a multi-tenant platform is complex. Roadie.co's challenge was to design, set up, and implement an environment and infrastructure for the new version of the platform release. The main thing was to be able to use artists' custom domains and pass all the traffic from the domain to the appropriate tenant/artist.",[1045,78825,78827,78831,78835,78840,78844,78848],{"className":78826},[1048,1049,1050,1051,1052],[1054,78828],{"description":78829,"icon":7560,"title":78830},"Talked to Roadie.co's developer and gathered the necessary data. Sent a ballpark estimate with a list of tasks.","Discovery",[1054,78832],{"description":78833,"icon":72248,"title":78834},"Installed the platform locally to understand backend, API, and frontend workings.","Platform Analysis",[1054,78836],{"description":78837,"icon":78838,"title":78839},"Assessed scalability for immediate needs and future growth.","i-lucide-scale","Scalability Assessment",[1054,78841],{"description":78842,"icon":50270,"title":78843},"Proposed the optimal solution by weighing pros and cons, considering limitations versus practicality.","Solution Design",[1054,78845],{"description":78846,"icon":6395,"title":78847},"Helped choose the best hosting provider and set up server and domain routing.","Hosting & Routing",[1054,78849],{"description":78850,"icon":1779,"title":78851},"Implemented continuous integration and deployment pipelines.","CI/CD Pipelines",[1572,78853,78854],{},[842,78855,78856],{},"Through this collaborative effort, MusicTech Lab helped successfully implement Roadie.co's updated platform, ensuring enhanced functionality and scalability to meet the evolving needs of artists and users alike.",[4937,78858],{},[863,78860,78862],{"id":78861},"navigating-the-road-to-success","Navigating the Road to Success",[842,78864,78865],{},"Roadie.co is more than just a platform — it's a game-changer for releasing artists and their teams. With its intuitive features, data-driven approach, and unwavering commitment to transparency, Roadie.co empowers artists to reach new heights of success in the music industry.",[4937,78867],{},[863,78869,51026],{"id":51025},[1045,78871,78873],{"className":78872},[13033,50238,50239,1052],[50241,78874],{"color":50243,"label":78875,"target":50245,"to":78876,"variant":50246},"Roadie.co","https://roadie.co/",{"title":728,"searchDepth":729,"depth":729,"links":78878},[78879,78880,78881,78882,78883,78884,78885,78886,78887],{"id":78677,"depth":729,"text":78678},{"id":78710,"depth":729,"text":78711},{"id":78734,"depth":729,"text":78735},{"id":78758,"depth":729,"text":78759},{"id":78780,"depth":729,"text":78781},{"id":78806,"depth":729,"text":78807},{"id":78819,"depth":729,"text":78820},{"id":78861,"depth":729,"text":78862},{"id":51025,"depth":729,"text":51026},{"name":78875,"logo":78889},"/images/logos/roadie.svg","2024-03-15T00:00:00.000Z","How Roadie.co helps independent artists build websites, manage tours, release music, and grow their fanbase with data-driven tools and email marketing.",{"src":78893},"/images/blog/musictechlab_blog_turn-fans-into-superfans-roadie-co.webp",{"enabled":738,"items":78895},[78896,78898,78900],{"text":78897,"icon":1067},"Multi-tenant platform with custom domain routing for independent artist websites.",{"text":78899,"icon":2895},"Artists own their data with full portability and no lock-in.",{"text":78901,"icon":9547},"Built-in tour management, release pages, mailing lists, and analytics for musicians.",{},{"title":58,"description":78891},[4990,5523],"2ijydv3IbdmuLJ2Yqvmbx5Ngu03UAHzZ2OLfJJRvSJM",{"id":78907,"title":646,"authors":78908,"badge":723,"body":78911,"category":756,"client":723,"date":79140,"description":79141,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":79142,"keyTakeaways":79144,"meta":79152,"navigation":738,"path":647,"seo":79153,"status":723,"stem":648,"tags":79154,"teaser":723,"__hash__":79155},"posts/blog/software-development/the-wtf-programming-scale-measuring-it-project-complexity.md",[78909],{"name":70989,"avatar":78910},{"src":70991},{"type":725,"value":78912,"toc":79117},[78913,78915,78919,78922,78924,78928,78931,78933,78937,78940,78942,78946,78949,78951,78955,78958,78960,78964,78967,78969,78973,78976,78978,78982,78985,78987,78991,78994,78996,79000,79003,79005,79009,79012,79014,79018,79021,79023,79027,79030,79032,79036,79039,79041,79045,79048,79050,79054,79057,79059,79063,79066,79068,79072,79075,79077,79081,79084,79090,79096,79102,79108,79110,79112,79115],[842,78914,40867],{},[863,78916,78918],{"id":78917},"the-scale-explained","The Scale Explained",[842,78920,78921],{},"The WTF Programming Scale (Whispers of The Forgotten Semicolon) is a 16-point metric, extending from Level 0 to Level 15, each indicating increasing complexity and potential frustration in IT projects.",[842,78923,52316],{},[1074,78925,78927],{"id":78926},"level-0-absolute-zen","Level 0: Absolute Zen",[842,78929,78930],{},"The mythical state where everything works on the first try. Projects at this level are more a utopian concept than reality, often used as a tale to motivate rookies.",[842,78932,52316],{},[1074,78934,78936],{"id":78935},"level-1-hello-world","Level 1: Hello, World!",[842,78938,78939],{},"Entry-Level Enlightenment. The simplest of tasks, offering a gentle introduction to the programming world.",[842,78941,52316],{},[1074,78943,78945],{"id":78944},"level-2-variables-and-loops","Level 2: Variables and Loops",[842,78947,78948],{},"Basic Sorcery. Introduces data manipulation and control flow, marking the first step into real programming.",[842,78950,52316],{},[1074,78952,78954],{"id":78953},"level-3-functions-and-recursion","Level 3: Functions and Recursion",[842,78956,78957],{},"Magical Incantations. The creation of callable units of code and the first brush with the mind-bending concept of recursion.",[842,78959,52316],{},[1074,78961,78963],{"id":78962},"level-4-debugging-basics-bug-hunting","Level 4: Debugging Basics - Bug Hunting",[842,78965,78966],{},"The initiation into the eternal battle against bugs, where patience and perseverance become key tools.",[842,78968,52316],{},[1074,78970,78972],{"id":78971},"level-5-version-control-hell-temporal-maze-navigation","Level 5: Version Control Hell - Temporal Maze Navigation.",[842,78974,78975],{},"Navigating the complexities of version control, including the dreaded merge conflicts.",[842,78977,52316],{},[1074,78979,78981],{"id":78980},"level-6-apis-and-integration-digital-diplomacy","Level 6: APIs and Integration - Digital Diplomacy",[842,78983,78984],{},"The challenge of making disparate systems communicate seamlessly.",[842,78986,52316],{},[1074,78988,78990],{"id":78989},"level-7-concurrency-and-parallelism-multitasking-mayhem","Level 7: Concurrency and Parallelism - Multitasking Mayhem",[842,78992,78993],{},"Writing code that efficiently does many things at once, introducing a whole new layer of complexity.",[842,78995,52316],{},[1074,78997,78999],{"id":78998},"level-8-legacy-code-archaeology-ancient-script-deciphering","Level 8: Legacy Code Archaeology - Ancient Script Deciphering",[842,79001,79002],{},"The daunting task of understanding and modifying code written by developers of yore.",[842,79004,52316],{},[1074,79006,79008],{"id":79007},"level-9-security-and-cryptography","Level 9: Security and Cryptography",[842,79010,79011],{},"Guardianship of the Digital Realm. Ensuring data integrity and secure communication in the face of ever-evolving threats.",[842,79013,52316],{},[1074,79015,79017],{"id":79016},"level-10-distributed-systems","Level 10: Distributed Systems",[842,79019,79020],{},"Orchestrating Complexity. Managing the behemoth of distributed computing, where components spread across networks must act in concert.",[842,79022,52316],{},[1074,79024,79026],{"id":79025},"level-11-artificial-intelligence-overlords","Level 11: Artificial Intelligence Overlords",[842,79028,79029],{},"Mentoring the Digital Mind. The creation and training of AI, venturing into the realm of machine learning and beyond.",[842,79031,52316],{},[1074,79033,79035],{"id":79034},"level-12-technological-fossil-fusion","Level 12: Technological Fossil Fusion",[842,79037,79038],{},"Archeological Coding Hybridity. Merging ancient and modern code in a digital excavation of web development practices.",[842,79040,52316],{},[1074,79042,79044],{"id":79043},"level-13-interdimensional-web-development","Level 13: Interdimensional Web Development",[842,79046,79047],{},"Cosmic User Experience Crafting. Designing web experiences that transcend traditional platforms and realities.",[842,79049,52316],{},[1074,79051,79053],{"id":79052},"level-14-cyber-physical-systems-and-iot-reality-programming","Level 14: Cyber-Physical Systems and IoT - Reality Programming",[842,79055,79056],{},"The fusion of digital and physical worlds through the Internet of Things, creating smart, interconnected environments.",[842,79058,52316],{},[1074,79060,79062],{"id":79061},"level-15-black-hole-of-code-existential-despair-debugging","Level 15: Black Hole of Code - Existential Despair Debugging",[842,79064,79065],{},"Facing code so complex, it threatens to consume all light and joy, challenging the very essence of one's being as a developer.",[842,79067,52316],{},[863,79069,79071],{"id":79070},"measurement-methodology-the-swear-word-index-swi","Measurement Methodology: The Swear Word Index (SWI)",[842,79073,79074],{},"The SWI measures the number of swear words uttered by team members per hour, serving as an innovative metric for project complexity. It provides a direct correlation between the emotional toll of a project and its place on the WTF Programming Scale.",[842,79076,52316],{},[863,79078,79080],{"id":79079},"applying-the-scale","Applying the Scale",[842,79082,79083],{},"To use the WTF Programming Scale and SWI in project assessment:",[842,79085,79086,79089],{},[996,79087,79088],{},"1. Establish a Baseline:"," Measure the SWI at the project's start to understand the team's initial state.",[842,79091,79092,79095],{},[996,79093,79094],{},"2. Monitor Regularly:"," Keep track of SWI fluctuations to identify spikes in frustration or complexity.",[842,79097,79098,79101],{},[996,79099,79100],{},"3. Map to Milestones:"," Relate changes in SWI to specific project phases to pinpoint complexity hotspots.",[842,79103,79104,79107],{},[996,79105,79106],{},"4. Intervene Accordingly:"," Use insights from the SWI and WTF Scale to make informed decisions on resource allocation, scope adjustments, or strategic shifts.",[842,79109,52316],{},[863,79111,18681],{"id":18680},[842,79113,79114],{},"The WTF Programming Scale, enhanced by the Swear Word Index, offers a novel approach to understanding the challenges of IT projects. By recognising the human element in software development, project managers can better navigate the complexities of their work, ensuring both project success and team well-being.",[842,79116,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":79118},[79119,79137,79138,79139],{"id":78917,"depth":729,"text":78918,"children":79120},[79121,79122,79123,79124,79125,79126,79127,79128,79129,79130,79131,79132,79133,79134,79135,79136],{"id":78926,"depth":1112,"text":78927},{"id":78935,"depth":1112,"text":78936},{"id":78944,"depth":1112,"text":78945},{"id":78953,"depth":1112,"text":78954},{"id":78962,"depth":1112,"text":78963},{"id":78971,"depth":1112,"text":78972},{"id":78980,"depth":1112,"text":78981},{"id":78989,"depth":1112,"text":78990},{"id":78998,"depth":1112,"text":78999},{"id":79007,"depth":1112,"text":79008},{"id":79016,"depth":1112,"text":79017},{"id":79025,"depth":1112,"text":79026},{"id":79034,"depth":1112,"text":79035},{"id":79043,"depth":1112,"text":79044},{"id":79052,"depth":1112,"text":79053},{"id":79061,"depth":1112,"text":79062},{"id":79070,"depth":729,"text":79071},{"id":79079,"depth":729,"text":79080},{"id":18680,"depth":729,"text":18681},"2024-03-14T00:00:00.000Z","A humorous yet insightful 16-point scale for measuring IT project complexity, from Absolute Zen to total chaos. A fun guide for developers and managers.",{"src":79143},"/images/blog/musictechlab_blog_the-wtf-programming-scale-measuring-it-project-complexity.webp",{"enabled":738,"items":79145},[79146,79148,79150],{"text":79147,"icon":3844},"The WTF Scale measures IT project complexity across 16 levels from Absolute Zen to existential despair.",{"text":79149,"icon":3847},"The Swear Word Index (SWI) tracks team frustration per hour as a proxy for project difficulty.",{"text":79151,"icon":50270},"Mapping SWI spikes to project milestones helps managers identify and address complexity hotspots.",{},{"title":646,"description":79141},[74615,18784],"ZdEzNJEixiRZAt3j7fMq5zjnLA2Nbq74PuoAXrLi6CE",{"id":79157,"title":662,"authors":79158,"badge":723,"body":79161,"category":756,"client":723,"date":79280,"description":79281,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":79282,"keyTakeaways":79284,"meta":79292,"navigation":738,"path":663,"seo":79293,"status":723,"stem":664,"tags":79294,"teaser":723,"__hash__":79295},"posts/blog/software-development/unifying-artists-and-audiences-exploring-music-glue.md",[79159],{"name":834,"to":720,"avatar":79160},{"src":722},{"type":725,"value":79162,"toc":79275},[79163,79172,79179,79189,79193,79199,79218,79220,79247,79252,79256],[842,79164,79165,79166,79171],{},"MusicTech Lab has partnered with ",[846,79167,79170],{"href":79168,"rel":79169},"https://www.musicglue.com/",[850],"Music Glue",", the direct-to-fan e-commerce platform built for the music industry. The partnership lets us recommend and integrate Music Glue's tools for artists, labels, and agents who need a single storefront for merch, music, and tickets.",[41054,79173,79174],{},[842,79175,79176],{},[964,79177,79178],{},"Music Glue sees big potential in the Central Eastern European market for our solutions. We're hoping teaming up with MusicTech Lab will be a win-win for both of us.",[842,79180,79181,79188],{},[996,79182,79183],{},[846,79184,79187],{"href":79185,"rel":79186},"https://www.linkedin.com/in/ACoAABMkdFwBj73clgapbNrnBvWOot40YbqjtSA",[850],"Mark Meharry"," — CEO, Music Glue",[863,79190,79192],{"id":79191},"what-music-glue-does","What Music Glue Does",[842,79194,79195,79198],{},[846,79196,79170],{"href":79168,"rel":79197},[850]," enables artists and their teams to sell merchandise, music, and tickets directly to fans worldwide — in one transaction — while retaining full ownership of customer data.",[1045,79200,79202,79206,79210,79214],{"className":79201},[1048,1049,1765,1051,1052],[1054,79203],{"description":79204,"title":79205},"Sell tickets, merch, and music through a single branded storefront. Mobile-ready websites custom-built around the artist's brand, with global distribution and customer support.","For Artists & Managers",[1054,79207],{"description":79208,"title":79209},"Fully managed direct-to-consumer campaigns with real-time reports and sales dashboards to monitor performance.","For Labels",[1054,79211],{"description":79212,"title":79213},"Independent, direct-to-fan ticketing with anti-tout technology, ballot tickets, and VIP solutions — all managed by Music Glue's ticketing team.","For Agents",[1054,79215],{"description":79216,"title":79217},"International distribution centres, localised customer support, and a single dashboard to monitor sales across all stores.","For Merchandise Companies",[863,79219,67121],{"id":67120},[1045,79221,79223,79227,79231,79235,79239,79243],{"className":79222},[1048,1049,1050,1051,1052],[1054,79224],{"description":79225,"title":79226},"Direct ticket sales with bundles, ballots, and VIP options. Anti-tout technology keeps tickets in fans' hands.","Ticketing",[1054,79228],{"description":79229,"title":79230},"Exclusive access, subscriber perks, and fan behaviour insights for targeted engagement.","Fan Club",[1054,79232],{"description":79233,"title":79234},"Sustainable custom merch with eco-friendly materials. No excess stock needed.","Print on Demand",[1054,79236],{"description":79237,"title":79238},"Integrate merch displays directly in YouTube channels. Setup takes minutes.","YouTube Merch Shelf",[1054,79240],{"description":79241,"title":79242},"Global chart eligibility with automated reporting to chart companies across multiple countries.","Chart Reporting",[1054,79244],{"description":79245,"title":79246},"International distribution centres across key regions for faster delivery and reduced postage rates.","Global Fulfillment",[1032,79248,79249],{},[842,79250,79251],{},"Music Glue also offers bundles (merch + album + tickets in one purchase), live streaming with ticket sales, and 24/7 multilingual customer support with an average resolution time under 2 hours.",[863,79253,79255],{"id":79254},"learn-more","Learn More",[1045,79257,79259,79261,79265,79269,79272],{"className":79258},[13033,50238,50239,1052],[50241,79260],{"color":50243,"label":69823,"target":50245,"to":79168,"variant":50246},[50241,79262],{"color":50249,"label":79263,"target":50245,"to":79264,"variant":50246},"Business Enquiries","https://www.musicglue.com/business-enquiries",[50241,79266],{"color":50249,"label":79267,"target":50245,"to":79268,"variant":50246},"Facebook","https://www.facebook.com/musicglue",[50241,79270],{"color":50249,"label":5665,"target":50245,"to":79271,"variant":50246},"https://www.instagram.com/musicglue",[50241,79273],{"color":50249,"label":58345,"target":50245,"to":79274,"variant":50246},"https://www.youtube.com/user/musicgluechannel",{"title":728,"searchDepth":729,"depth":729,"links":79276},[79277,79278,79279],{"id":79191,"depth":729,"text":79192},{"id":67120,"depth":729,"text":67121},{"id":79254,"depth":729,"text":79255},"2024-02-28T00:00:00.000Z","Music Glue specialises in creating tailored e-commerce solutions for the music industry. Learn more about MusicTech Lab's partnership with Music Glue.",{"src":79283},"/images/blog/musictechlab_blog_unifying-artists-and-audiences-exploring-music-glue.webp",{"enabled":738,"items":79285},[79286,79288,79290],{"text":79287,"icon":9547},"Music Glue lets artists sell merch, music, and tickets in a single transaction with full data ownership.",{"text":79289,"icon":7495},"Anti-tout technology and ballot ticketing keep tickets in fans' hands instead of resellers.",{"text":79291,"icon":12412},"Print-on-demand merch uses eco-friendly materials and eliminates excess inventory risk.",{},{"title":662,"description":79281},[5523,52276],"gOslZNjf98FBRPh8lGeT-DCghypBGiX_XE9QEd8oZWQ",{"id":79297,"title":374,"authors":79298,"badge":723,"body":79301,"category":756,"client":723,"date":79427,"description":79428,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":79429,"keyTakeaways":79431,"meta":79441,"navigation":738,"path":375,"seo":79442,"status":723,"stem":376,"tags":79443,"teaser":723,"__hash__":79444},"posts/blog/software-development/bravelab-partners-with-the-audio-lalal-ai.md",[79299],{"name":834,"to":720,"avatar":79300},{"src":722},{"type":725,"value":79302,"toc":79421},[79303,79311,79315,79325,79340,79344,79347,79373,79377,79384,79399,79404,79406],[842,79304,79165,79305,79310],{},[846,79306,79309],{"href":79307,"rel":79308},"https://www.lalal.ai/",[850],"LALAL.AI",", the AI-powered vocal remover and music source separation service. The partnership connects LALAL.AI's stem splitting technology with MusicTech Lab's experience building music tech products — giving clients a clear path from idea to integration.",[863,79312,79314],{"id":79313},"what-lalalai-does","What LALAL.AI Does",[842,79316,79317,79320,79321,79324],{},[846,79318,79309],{"href":79307,"rel":79319},[850]," uses neural networks to split audio into individual stems — vocals, drums, bass, piano, electric guitar, acoustic guitar, and synthesizer — without compromising quality. Based in Zug, Switzerland, the company has processed over ",[996,79322,79323],{},"335 million hours"," of audio since launch.",[1045,79326,79328,79332,79336],{"className":79327},[1048,1049,1050,1051,1052],[1054,79329],{"description":79330,"title":79331},"Isolate or remove vocals from any track. Essential for karaoke, remixes, and sample clearance workflows.","Vocal Removal",[1054,79333],{"description":79334,"title":79335},"Extract up to 9 individual stems from a single audio file — drums, bass, piano, guitars, synths, and more.","Stem Separation",[1054,79337],{"description":79338,"title":79339},"A developer-friendly API that lets you embed stem splitting directly into your own apps and services.","API Access",[863,79341,79343],{"id":79342},"use-cases","Use Cases",[842,79345,79346],{},"Stem separation unlocks workflows that weren't possible a few years ago. Here are some of the most common:",[958,79348,79349,79355,79361,79367],{},[961,79350,79351,79354],{},[996,79352,79353],{},"Remix and mashup production"," — producers can isolate vocals or instrumentals from existing tracks without needing access to the original session files",[961,79356,79357,79360],{},[996,79358,79359],{},"Music education"," — teachers and students can strip away specific instruments to practice along with a real recording",[961,79362,79363,79366],{},[996,79364,79365],{},"Content creation"," — podcasters, YouTubers, and video editors can extract clean vocals or remove background music from clips",[961,79368,79369,79372],{},[996,79370,79371],{},"DJ sets and live performance"," — DJs can create acapellas and instrumentals on the fly for live remixing",[863,79374,79376],{"id":79375},"what-the-partnership-means","What the Partnership Means",[842,79378,79379,79380,79383],{},"MusicTech Lab serves as a technical partner for ",[846,79381,79309],{"href":79307,"rel":79382},[850],". If you're a musician, sound engineer, DJ, or music tech company looking to use stem separation in your workflow, we can help you:",[1045,79385,79387,79391,79395],{"className":79386},[1048,1049,1050,1051,1052],[1054,79388],{"description":79389,"title":79390},"Evaluate how LALAL.AI fits your specific audio processing needs.","Understand",[1054,79392],{"description":79393,"title":79394},"Tailor the solution to your technical requirements and pipeline.","Customize",[1054,79396],{"description":79397,"title":79398},"Connect LALAL.AI's API with whatever tools you already use.","Integrate",[1032,79400,79401],{},[842,79402,79403],{},"LALAL.AI is trusted by producers, DJs, and content creators worldwide. With 335M+ hours of audio processed, the technology is production-ready and battle-tested.",[863,79405,79255],{"id":79254},[1045,79407,79409,79412,79415,79418],{"className":79408},[13033,50238,50239,1052],[50241,79410],{"color":50243,"label":79411,"target":50245,"to":79307,"variant":50246},"LALAL.AI Website",[50241,79413],{"color":50249,"label":15320,"target":50245,"to":79414,"variant":50246},"https://github.com/OmniSaleGmbH/lalalai",[50241,79416],{"color":50249,"label":79267,"target":50245,"to":79417,"variant":50246},"https://www.facebook.com/Lalalai-106143107757872/",[50241,79419],{"color":50249,"label":58345,"target":50245,"to":79420,"variant":50246},"https://www.youtube.com/channel/UCawy7BDDJ62QwQeeRjYmEtQ",{"title":728,"searchDepth":729,"depth":729,"links":79422},[79423,79424,79425,79426],{"id":79313,"depth":729,"text":79314},{"id":79342,"depth":729,"text":79343},{"id":79375,"depth":729,"text":79376},{"id":79254,"depth":729,"text":79255},"2024-02-01T00:00:00.000Z","MusicTech Lab partners with LALAL.AI, the AI-powered vocal remover and music source separation service, to bring stem splitting into real-world music tech workflows.",{"src":79430},"/images/blog/musictechlab_blog_musictechlab-partners-with-the-audio-lalal-ai.webp",{"enabled":738,"items":79432},[79433,79435,79437,79439],{"text":79434,"icon":11614},"LALAL.AI uses neural networks to split audio into up to 9 individual stems.",{"text":79436,"icon":3844},"Over 335 million hours of audio have been processed through the platform.",{"text":79438,"icon":9547},"Use cases include remix production, music education, content creation, and live DJ sets.",{"text":79440,"icon":11617},"MusicTech Lab serves as a technical integration partner for LALAL.AI clients.",{},{"title":374,"description":79428},[14100,5523],"oiKtY36JLlyG0U8i9mEyz9RaMXICMtdmoMXvb2CLWBY",{"id":79446,"title":188,"authors":79447,"badge":723,"body":79450,"category":731,"client":723,"date":79724,"description":79725,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":79726,"keyTakeaways":79728,"meta":79738,"navigation":738,"path":189,"seo":79739,"status":723,"stem":190,"tags":79740,"teaser":723,"__hash__":79741},"posts/blog/music-data/understanding-the-api-first-approach.md",[79448],{"name":74344,"avatar":79449},{"src":74346},{"type":725,"value":79451,"toc":79708},[79452,79455,79458,79461,79464,79467,79471,79486,79500,79504,79516,79519,79523,79539,79552,79556,79559,79575,79579,79601,79605,79608,79622,79626,79648,79656,79660,79673,79676,79680,79683,79703],[842,79453,79454],{},"In e-commerce, virtual shops, payment gateways, delivery services, and CRMs all need to interact seamlessly. The API First approach puts those connectors at the forefront of development — designing APIs before writing the application code itself.",[842,79456,79457],{},"This article explores why API First matters, how leading e-commerce platforms use it, and what it means for your business.",[863,79459,188],{"id":79460},"understanding-the-api-first-approach",[842,79462,79463],{},"APIs are the glue connecting software applications. They define how systems communicate — sets of rules and protocols for sharing data and functionality. In traditional development, APIs were an afterthought, retrofitted into existing systems. The API First approach flips this: you design the API contract (methods, data formats, endpoints) before writing any application code.",[842,79465,79466],{},"This ensures the API is a stable foundation, not a bolt-on. It's especially valuable for e-commerce platforms that need to support many integrations and interaction models.",[1074,79468,79470],{"id":79469},"why-api-first-shines-in-e-commerce","Why API First shines in e-commerce",[1045,79472,79474,79478,79482],{"className":79473},[1048,1049,1050,1051,1052],[1054,79475],{"description":79476,"icon":1769,"title":79477},"Frontend delivers rich UX while backend focuses on business logic. Modular, loosely coupled systems evolve independently.","Decoupled Architecture",[1054,79479],{"description":79480,"icon":52124,"title":79481},"Payment gateways, CRM systems, logistics providers, and third-party services connect effortlessly through well-defined APIs.","Seamless Integrations",[1054,79483],{"description":79484,"icon":4855,"title":79485},"Mobile apps, social media platforms, IoT devices — APIs enable consistent experiences across every touchpoint.","Multi-Channel Ready",[842,79487,79488,79489,5660,79492,79495,79496,79499],{},"Several leading e-commerce platforms have embraced the API First approach: ",[996,79490,79491],{},"Saleor",[996,79493,79494],{},"Reaction Commerce"," (now Mailchimp Open Commerce), and ",[996,79497,79498],{},"Magento",". Each illustrates how designing with an API First mindset leads to extensible and scalable solutions.",[863,79501,79503],{"id":79502},"reaction-commerce-powering-custom-e-commerce-experiences","Reaction Commerce: Powering Custom E-commerce Experiences",[1045,79505,79507,79511],{"className":79506},[1048,1049,1765,1051,1052],[1054,79508],{"description":79509,"icon":2939,"title":79510},"Built entirely with JavaScript (Node.js + React), Reaction Commerce delivers highly reactive, customizable e-commerce experiences with real-time data updates.","Real-Time Reactive Platform",[1054,79512],{"description":79513,"icon":79514,"title":79515},"Clients request exactly the data they need — no over-fetching. Complex nested structures in a single call simplify client-side code.","i-lucide-braces","GraphQL-Powered API",[842,79517,79518],{},"JavaScript's asynchronous nature and vast ecosystem of libraries make Reaction Commerce a highly productive environment. Developers work in a language they already know, contributing to faster development and interactive real-time experiences.",[1074,79520,79522],{"id":79521},"real-world-applications","Real-world applications",[1045,79524,79526,79531,79535],{"className":79525},[1048,1049,1050,1051,1052],[1054,79527],{"description":79528,"icon":79529,"title":79530},"LA-based design studio leveraging real-time inventory management. API First enables seamless CRM and logistics integration for streamlined order fulfillment.","i-lucide-sofa","Stephen Kenn",[1054,79532],{"description":79533,"icon":9991,"title":79534},"Built a Fortune 10 company's internal software provisioning tool. Integrated Microsoft Active Directory, MongoDB sync, and custom checkout — all via Reaction Commerce APIs.","Project Ricochet",[1054,79536],{"description":79537,"icon":8737,"title":79538},"Digital agency praising Reaction Commerce's modular architecture. Created boilerplate plugins demonstrating how internal modules become standalone NPM packages.","DemandCluster",[1572,79540,79541],{},[842,79542,79543,79544,79547,79548,79551],{},"The value of API First was validated when ",[996,79545,79546],{},"Mailchimp acquired Reaction Commerce"," in April 2020 for $16.1 million. The platform is now known as ",[996,79549,79550],{},"Mailchimp Open Commerce",", integrated into Mailchimp's developer-focused product suite.",[863,79553,79555],{"id":79554},"magento-the-enterprise-api-first-solution","Magento: The Enterprise API First Solution",[842,79557,79558],{},"Built on PHP with Zend Framework and Symfony components, Magento is a cornerstone of enterprise e-commerce. Under Adobe's ownership since 2018, it integrates with Adobe's full suite of business tools. In 2021, Magento became a global leader in the Gartner Magic Quadrant for Digital Commerce — and API First was a key reason.",[1045,79560,79562,79566,79570],{"className":79561},[1048,1049,1050,1051,1052],[1054,79563],{"description":79564,"icon":1057,"title":79565},"Extensive REST APIs for CRM, ERP, payment gateways, product management, and customer data — all built API First.","Enterprise-Grade APIs",[1054,79567],{"description":79568,"icon":3920,"title":79569},"Supports businesses from startups to international operations. Flexible architecture allows adding, modifying, and expanding functionality.","Built to Scale",[1054,79571],{"description":79572,"icon":79573,"title":79574},"Named a global leader in the 2021 Magic Quadrant for Digital Commerce, powered by its API First architecture.","i-lucide-award","Gartner Leader",[1074,79576,79578],{"id":79577},"who-uses-magento","Who uses Magento?",[1045,79580,79582,79587,79592,79597],{"className":79581},[1048,1049,1765,1051,1052],[1054,79583],{"description":79584,"icon":79585,"title":79586},"Powers Ford's website and global dealer network for original accessories. Personalization features let visitors filter products by vehicle model. Revenue: $127B (2020).","i-lucide-car","Ford Motor Company",[1054,79588],{"description":79589,"icon":79590,"title":79591},"Runs a Magento 2 store for bottles, apparel, and collectibles. Customers personalize products with their names and earn rewards through vending machine purchases.","i-lucide-cup-soda","Coca-Cola",[1054,79593],{"description":79594,"icon":79595,"title":79596},"B2B supplier portal powered by Magento's company accounts, shared catalogs, custom pricing, and ERP integrations. Revenue: $71B (2020).","i-lucide-factory","Procter & Gamble",[1054,79598],{"description":79599,"icon":72248,"title":79600},"Click & collect shopping across India, omnichannel experiences in China and Hong Kong, plus customer rewards programs — all on Magento 2.","HP Inc.",[863,79602,79604],{"id":79603},"saleor-empowering-e-commerce-with-an-api-first-mindset","Saleor: Empowering E-commerce with an API First Mindset",[842,79606,79607],{},"Saleor is an open-source platform crafted with Python and Django, designed for extensibility, scalability, and customization. It uses GraphQL for efficient data queries — clients request exactly what they need, reducing network overhead and improving performance.",[1045,79609,79611,79615,79619],{"className":79610},[1048,1049,1050,1051,1052],[1054,79612],{"description":79613,"icon":8737,"title":79614},"Independent modules communicate via well-defined interfaces. Add features, swap services, or change business logic without disrupting the system.","Modular Architecture",[1054,79616],{"description":79617,"icon":79514,"title":79618},"Efficient data fetching, rapid feature development, and easy integration with external systems — all through a single, flexible API layer.","GraphQL API",[1054,79620],{"description":79621,"icon":8790,"title":71775},"Comprehensive, well-structured docs covering data types, mutations, queries, and endpoints. Reduces onboarding time significantly.",[1074,79623,79625],{"id":79624},"who-uses-saleor","Who uses Saleor?",[1045,79627,79629,79634,79639,79644],{"className":79628},[1048,1049,1765,1051,1052],[1054,79630],{"description":79631,"icon":79632,"title":79633},"The creators of Saleor use their own platform — a responsive, user-friendly site showcasing the platform's flexibility and adaptability.","i-lucide-code-2","Mirumee Software",[1054,79635],{"description":79636,"icon":79637,"title":79638},"Relaunched website and app with Saleor, placing digital ethics at the core. Real-time updates, complex product data, and seamless user experience at scale.","i-lucide-leaf","Lush Labs",[1054,79640],{"description":79641,"icon":79642,"title":79643},"Swiss/Liechtenstein marketplace connecting farmers, artisans, and small businesses with customers in a niche regional products market.","i-lucide-wheat","Hofkorb",[1054,79645],{"description":79646,"icon":5176,"title":79647},"Saudi Arabian platform connecting fashion designers, celebrities, and buyers with listing, promotion, and affiliate marketing capabilities.","Wecre8",[1032,79649,79650],{},[842,79651,79652,79655],{},[996,79653,79654],{},"Colophon Foundry",", a renowned international type foundry, showcases Saleor in a niche market — leveraging the GraphQL API for real-time inventory management of complex typeface product data.",[863,79657,79659],{"id":79658},"comparing-api-first-e-commerce-solutions","Comparing API First E-commerce Solutions",[1045,79661,79663,79667,79670],{"className":79662},[1048,1049,1050,1051,1052],[1054,79664],{"description":79665,"icon":79666,"title":79491},"Python + Django, GraphQL API. Modular plugin architecture. Best for complex product variants and high UI customisation.","i-lucide-egg",[1054,79668],{"description":79669,"icon":2939,"title":79494},"Node.js + React, GraphQL API. Real-time reactive architecture. Best for applications demanding instant updates during high-volume sales.",[1054,79671],{"description":79672,"icon":66080,"title":79498},"PHP + Zend/Symfony, REST APIs. Mature enterprise stack with Adobe integrations. Comprehensive catalog, CRM, and B2B features.",[842,79674,79675],{},"All three share the API First approach as a common denominator. The key differences lie in the technology stack, developer experience, and where each platform excels.",[863,79677,79679],{"id":79678},"overcoming-challenges-and-future-considerations","Overcoming Challenges and Future Considerations",[842,79681,79682],{},"The API First approach offers significant advantages but presents challenges that need careful attention.",[1045,79684,79686,79690,79694,79698],{"className":79685},[1048,1049,1765,1051,1052],[1054,79687],{"description":79688,"icon":7495,"title":79689},"Implement OAuth/JWT for authentication, role-based access control for authorization, and SSL/TLS for encryption. Schedule regular security audits and penetration testing.","Prioritize Security",[1054,79691],{"description":79692,"icon":1779,"title":79693},"Use semantic versioning and clear deprecation strategies. URI or media type versioning helps maintain backward compatibility as APIs evolve.","Manage API Lifecycle",[1054,79695],{"description":79696,"icon":3850,"title":79697},"Use Swagger or Postman to auto-generate and maintain API docs. Comprehensive documentation is a hallmark of the API First approach.","Invest in Documentation",[1054,79699],{"description":79700,"icon":79701,"title":79702},"Headless commerce decouples frontend from backend. Microservices promote scalability and resilience with independent deployment.","i-lucide-boxes","Embrace Modern Architecture",[1032,79704,79705],{},[842,79706,79707],{},"The API First approach is not just a technical decision — it's a business strategy. By designing APIs before code, you create a stable foundation that supports multiple channels, integrations, and future growth.",{"title":728,"searchDepth":729,"depth":729,"links":79709},[79710,79713,79716,79719,79722,79723],{"id":79460,"depth":729,"text":188,"children":79711},[79712],{"id":79469,"depth":1112,"text":79470},{"id":79502,"depth":729,"text":79503,"children":79714},[79715],{"id":79521,"depth":1112,"text":79522},{"id":79554,"depth":729,"text":79555,"children":79717},[79718],{"id":79577,"depth":1112,"text":79578},{"id":79603,"depth":729,"text":79604,"children":79720},[79721],{"id":79624,"depth":1112,"text":79625},{"id":79658,"depth":729,"text":79659},{"id":79678,"depth":729,"text":79679},"2024-01-29T00:00:00.000Z","Why the API First approach matters for e-commerce. Learn how designing APIs before code leads to scalable, maintainable, integration-friendly platforms.",{"src":79727},"/images/blog/musictechlab_blog_understanding-the-api-first-approach.webp",{"enabled":738,"items":79729},[79730,79732,79734,79736],{"text":79731,"icon":5365},"API First means designing the API contract before writing any application code.",{"text":79733,"icon":8737},"Saleor, Reaction Commerce, and Magento all built their e-commerce platforms API First.",{"text":79735,"icon":5504},"Mailchimp acquired Reaction Commerce for $16.1M, validating the API First approach.",{"text":79737,"icon":2939},"GraphQL reduces over-fetching by letting clients request exactly the data they need.",{},{"title":188,"description":79725},[18784,5523,11843],"WN99ZqOypx_ZYXzZCCthV-dVe_3CRO7zM6Qj8V7tM94",{"id":79743,"title":378,"authors":79744,"badge":723,"body":79747,"category":756,"client":723,"date":79844,"description":79845,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":79846,"keyTakeaways":79848,"meta":79856,"navigation":738,"path":379,"seo":79857,"status":723,"stem":380,"tags":79858,"teaser":723,"__hash__":79859},"posts/blog/software-development/bravelab-partners-with-the-audio-programmer.md",[79745],{"name":834,"to":720,"avatar":79746},{"src":722},{"type":725,"value":79748,"toc":79840},[79749,79756,79760,79780,79795,79819,79823,79826,79829],[842,79750,79165,79751,79755],{},[846,79752,54811],{"href":79753,"rel":79754},"https://www.theaudioprogrammer.com/",[850],", the largest online community for audio development. The goal is simple: connect audio developers with real-world music tech projects, and bring deeper technical knowledge into how we build products.",[863,79757,79759],{"id":79758},"who-is-the-audio-programmer","Who Is The Audio Programmer?",[842,79761,79762,79769,79770,79773,79774,79779],{},[996,79763,79764],{},[846,79765,79768],{"href":79766,"rel":79767},"https://www.linkedin.com/in/joshhodge/",[850],"Joshua Hodge"," started ",[846,79771,54811],{"href":79753,"rel":79772},[850]," in 2017 as a ",[846,79775,79778],{"href":79776,"rel":79777},"https://www.youtube.com/theaudioprogrammer",[850],"YouTube channel",", teaching people how to build their own audio apps and plug-ins. It has since grown into a global community of over 40,000 developers, producers, and audio enthusiasts. The company, based in London, now operates across three areas:",[1045,79781,79783,79787,79791],{"className":79782},[1048,1049,1050,1051,1052],[1054,79784],{"description":79785,"title":79786},"Tutorials, books, live meetups, and resources covering VST plugin development, DSP, [JUCE](https://juce.com/), Rust for audio, and AI in music production.","Learn",[1054,79788],{"description":79789,"title":79790},"Recruitment services connecting companies with experienced audio developers and specialists.","Hire",[1054,79792],{"description":79793,"title":79794},"Custom audio plugin and app development with an in-house team of audio engineers.","Create",[1032,79796,79797],{},[842,79798,79799,79800,5660,79805,5660,79810,7378,79814,79818],{},"Trusted by companies like ",[846,79801,79804],{"href":79802,"rel":79803},"https://www.ableton.com/",[850],"Ableton",[846,79806,79809],{"href":79807,"rel":79808},"https://focusrite.com/",[850],"Focusrite",[846,79811,55848],{"href":79812,"rel":79813},"https://www.native-instruments.com/",[850],[846,79815,53411],{"href":79816,"rel":79817},"https://www.universalmusic.com/",[850]," — The Audio Programmer is where audio developers go to learn, collaborate, and find work.",[863,79820,79822],{"id":79821},"what-this-partnership-means","What This Partnership Means",[842,79824,79825],{},"For MusicTech Lab, this partnership opens a direct line to the audio development world. When our clients need custom audio processing, plug-in development, or DSP expertise, we can tap into a network of specialists who live and breathe this stuff.",[842,79827,79828],{},"For The Audio Programmer community, it means access to real projects — from streaming platforms and royalty engines to music production tools. The kind of work that turns side skills into professional experience.",[1045,79830,79832,79834,79836],{"className":79831},[13033,50238,50239,1052],[50241,79833],{"color":50243,"label":69823,"target":50245,"to":79753,"variant":50246},[50241,79835],{"color":50249,"label":58345,"target":50245,"to":79776,"variant":50246},[50241,79837],{"color":50249,"label":79838,"target":50245,"to":79839,"variant":50246},"Discord","https://www.theaudioprogrammer.com/discord",{"title":728,"searchDepth":729,"depth":729,"links":79841},[79842,79843],{"id":79758,"depth":729,"text":79759},{"id":79821,"depth":729,"text":79822},"2024-01-22T00:00:00.000Z","MusicTech Lab partners with The Audio Programmer community to connect audio developers with real-world music tech projects and deepen collaboration in audio development.",{"src":79847},"/images/blog/musictechlab_blog_musictechlab-partners-with-the-audio-programmer.webp",{"enabled":738,"items":79849},[79850,79852,79854],{"text":79851,"icon":4845},"The Audio Programmer community has over 40,000 audio developers and producers.",{"text":79853,"icon":9547},"Trusted by Ableton, Focusrite, Native Instruments, and Universal Music Group.",{"text":79855,"icon":11617},"Partnership connects audio dev specialists with real-world music tech projects.",{},{"title":378,"description":79845},[5523,26062],"RVvwwibQQQRDpssN2ytFiOYLE9yf0-nbhY9M00Lm8UM",{"id":79861,"title":156,"authors":79862,"badge":723,"body":79867,"category":731,"client":723,"date":82651,"description":82652,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":82653,"keyTakeaways":82655,"meta":82665,"navigation":738,"path":157,"seo":82666,"status":723,"stem":158,"tags":82667,"teaser":723,"__hash__":82668},"posts/blog/music-data/introduction-to-generating-ddex-file-using-python.md",[79863],{"name":79864,"avatar":79865},"Andrzej Dąbski",{"src":79866},"/images/people/andrzej-dabski.png",{"type":725,"value":79868,"toc":82642},[79869,79873,79876,79895,79898,79902,79911,79914,79934,79938,79945,80691,80695,80698,82578,82583,82587,82610,82614,82617,82639],[863,79870,79872],{"id":79871},"what-is-a-ddex-file","What is a DDEX file?",[842,79874,79875],{},"DDEX (Digital Data Exchange) is a set of international standards for exchanging digital media data in the music industry. These XML-based standards ensure that information about releases, licensing, recordings, and sales flows accurately between creators, distributors, and retailers.",[1045,79877,79879,79883,79887,79891],{"className":79878},[1048,1049,1765,1051,1052],[1054,79880],{"description":79881,"title":79882},"Communicates release information — albums, singles, tracklists, and contributors.","ERN (Electronic Release Notification)",[1054,79884],{"description":79885,"title":79886},"Reports sales and usage data for digital music releases across platforms.","DSR (Digital Sales Reporting)",[1054,79888],{"description":79889,"title":79890},"Exchanges details about the recording process, contributors, and technical specs.","RIN (Recording Information Notification)",[1054,79892],{"description":79893,"title":79894},"Supports licensing data exchange between publishers and licensing agencies.","MLC (Musical Works Licensing Creator)",[842,79896,79897],{},"The most common use case today is proving rights to a piece of music (clearance) or sending takedown messages when content is used without permission. Different content providers support different DDEX versions — YouTube supports 3.4 to 3.8, Facebook supports 3.8.3, and the newest version is 4.2.",[863,79899,79901],{"id":79900},"how-to-generate-a-ddex-file-using-python","How to Generate a DDEX File Using Python",[842,79903,79904,79905,79910],{},"The simplest approach is to start from an existing DDEX file and create a template. ",[846,79906,79909],{"href":79907,"rel":79908},"https://www.octolivery.com/",[850],"Octolivery"," is one of the few DDEX file generators available.",[842,79912,79913],{},"To fill the template, you'll need:",[958,79915,79916,79919,79928,79931],{},[961,79917,79918],{},"A link to the already-published music or video",[961,79920,79921,79922,79927],{},"Your ",[846,79923,79926],{"href":79924,"rel":79925},"https://dpid.ddex.net/",[850],"DPID"," (free to obtain — no DDEX membership required)",[961,79929,79930],{},"The DDEX ID of the recipient company",[961,79932,79933],{},"Track data: name, duration, artists, compositor, etc. (per scene for music videos)",[1074,79935,79937],{"id":79936},"basic-example-ern-xml-with-python","Basic Example: ERN XML with Python",[842,79939,79940,79941,79944],{},"Here's a minimal example using Python's built-in ",[895,79942,79943],{},"xml.etree.ElementTree"," to create a DDEX ERN file:",[1013,79946,79948],{"className":1368,"code":79947,"language":1250,"meta":728,"style":728},"import xml.etree.ElementTree as ET\n\n# Create root element\nroot = ET.Element(\"ern:NewReleaseMessage\", {\n    \"xmlns:ern\": \"http://ddex.net/xml/ern/43\",\n    \"MessageSchemaVersionId\": \"ern/43\",\n})\n\n# Message header\nheader = ET.SubElement(root, \"MessageHeader\")\nET.SubElement(header, \"MessageId\").text = \"MSG-2023-001\"\nET.SubElement(header, \"MessageCreatedDateTime\").text = \"2023-12-07T10:00:00Z\"\n\nsender = ET.SubElement(header, \"MessageSender\")\nET.SubElement(sender, \"PartyId\").text = \"PADPIDA2023010101A\"\nET.SubElement(sender, \"PartyName\").text = \"My Label\"\n\nrecipient = ET.SubElement(header, \"MessageRecipient\")\nET.SubElement(recipient, \"PartyId\").text = \"PADPIDA2020121501Y\"\nET.SubElement(recipient, \"PartyName\").text = \"YouTube\"\n\n# Resource list\nresource_list = ET.SubElement(root, \"ResourceList\")\nsound_recording = ET.SubElement(resource_list, \"SoundRecording\")\nET.SubElement(sound_recording, \"ResourceReference\").text = \"A1\"\n\ndetails = ET.SubElement(sound_recording, \"SoundRecordingDetailsByTerritory\")\ntitle = ET.SubElement(details, \"Title\")\nET.SubElement(title, \"TitleText\").text = \"My Song Title\"\nET.SubElement(details, \"Duration\").text = \"PT3M45S\"\n\n# Write to file\ntree = ET.ElementTree(root)\nET.indent(tree, space=\"  \")\ntree.write(\"release.xml\", xml_declaration=True, encoding=\"UTF-8\")\n\nprint(\"DDEX ERN file generated: release.xml\")\n",[895,79949,79950,79972,79976,79981,80009,80029,80049,80053,80057,80062,80092,80127,80161,80165,80193,80228,80261,80265,80293,80327,80359,80363,80368,80395,80423,80456,80460,80487,80514,80547,80580,80584,80589,80608,80635,80672,80676],{"__ignoreMap":728},[1086,79951,79952,79954,79957,79959,79962,79964,79967,79969],{"class":1088,"line":1089},[1086,79953,6503],{"class":1423},[1086,79955,79956],{"class":1436}," xml",[1086,79958,861],{"class":1146},[1086,79960,79961],{"class":4109},"etree",[1086,79963,861],{"class":1146},[1086,79965,79966],{"class":4109},"ElementTree",[1086,79968,19873],{"class":1423},[1086,79970,79971],{"class":1436}," ET\n",[1086,79973,79974],{"class":1088,"line":729},[1086,79975,3390],{"emptyLinePlaceholder":738},[1086,79977,79978],{"class":1088,"line":1112},[1086,79979,79980],{"class":1427},"# Create root element\n",[1086,79982,79983,79986,79988,79991,79993,79996,79998,80000,80003,80005,80007],{"class":1088,"line":1181},[1086,79984,79985],{"class":1436},"root ",[1086,79987,1440],{"class":1146},[1086,79989,79990],{"class":1436}," ET",[1086,79992,861],{"class":1146},[1086,79994,79995],{"class":1105},"Element",[1086,79997,1398],{"class":1146},[1086,79999,1159],{"class":1146},[1086,80001,80002],{"class":1096},"ern:NewReleaseMessage",[1086,80004,1159],{"class":1146},[1086,80006,1227],{"class":1146},[1086,80008,1164],{"class":1146},[1086,80010,80011,80013,80016,80018,80020,80022,80025,80027],{"class":1088,"line":1205},[1086,80012,1169],{"class":1146},[1086,80014,80015],{"class":1096},"xmlns:ern",[1086,80017,1159],{"class":1146},[1086,80019,1133],{"class":1146},[1086,80021,1195],{"class":1146},[1086,80023,80024],{"class":1096},"http://ddex.net/xml/ern/43",[1086,80026,1159],{"class":1146},[1086,80028,1202],{"class":1146},[1086,80030,80031,80033,80036,80038,80040,80042,80045,80047],{"class":1088,"line":1276},[1086,80032,1169],{"class":1146},[1086,80034,80035],{"class":1096},"MessageSchemaVersionId",[1086,80037,1159],{"class":1146},[1086,80039,1133],{"class":1146},[1086,80041,1195],{"class":1146},[1086,80043,80044],{"class":1096},"ern/43",[1086,80046,1159],{"class":1146},[1086,80048,1202],{"class":1146},[1086,80050,80051],{"class":1088,"line":1282},[1086,80052,1567],{"class":1146},[1086,80054,80055],{"class":1088,"line":1288},[1086,80056,3390],{"emptyLinePlaceholder":738},[1086,80058,80059],{"class":1088,"line":2685},[1086,80060,80061],{"class":1427},"# Message header\n",[1086,80063,80064,80067,80069,80071,80073,80076,80078,80081,80083,80085,80088,80090],{"class":1088,"line":2700},[1086,80065,80066],{"class":1436},"header ",[1086,80068,1440],{"class":1146},[1086,80070,79990],{"class":1436},[1086,80072,861],{"class":1146},[1086,80074,80075],{"class":1105},"SubElement",[1086,80077,1398],{"class":1146},[1086,80079,80080],{"class":1105},"root",[1086,80082,1227],{"class":1146},[1086,80084,1195],{"class":1146},[1086,80086,80087],{"class":1096},"MessageHeader",[1086,80089,1159],{"class":1146},[1086,80091,1455],{"class":1146},[1086,80093,80094,80097,80099,80101,80103,80105,80107,80109,80112,80114,80116,80118,80120,80122,80125],{"class":1088,"line":3398},[1086,80095,80096],{"class":1436},"ET",[1086,80098,861],{"class":1146},[1086,80100,80075],{"class":1105},[1086,80102,1398],{"class":1146},[1086,80104,60749],{"class":1105},[1086,80106,1227],{"class":1146},[1086,80108,1195],{"class":1146},[1086,80110,80111],{"class":1096},"MessageId",[1086,80113,1159],{"class":1146},[1086,80115,6786],{"class":1146},[1086,80117,1018],{"class":4109},[1086,80119,19552],{"class":1146},[1086,80121,1195],{"class":1146},[1086,80123,80124],{"class":1096},"MSG-2023-001",[1086,80126,4441],{"class":1146},[1086,80128,80129,80131,80133,80135,80137,80139,80141,80143,80146,80148,80150,80152,80154,80156,80159],{"class":1088,"line":1715},[1086,80130,80096],{"class":1436},[1086,80132,861],{"class":1146},[1086,80134,80075],{"class":1105},[1086,80136,1398],{"class":1146},[1086,80138,60749],{"class":1105},[1086,80140,1227],{"class":1146},[1086,80142,1195],{"class":1146},[1086,80144,80145],{"class":1096},"MessageCreatedDateTime",[1086,80147,1159],{"class":1146},[1086,80149,6786],{"class":1146},[1086,80151,1018],{"class":4109},[1086,80153,19552],{"class":1146},[1086,80155,1195],{"class":1146},[1086,80157,80158],{"class":1096},"2023-12-07T10:00:00Z",[1086,80160,4441],{"class":1146},[1086,80162,80163],{"class":1088,"line":3409},[1086,80164,3390],{"emptyLinePlaceholder":738},[1086,80166,80167,80170,80172,80174,80176,80178,80180,80182,80184,80186,80189,80191],{"class":1088,"line":3415},[1086,80168,80169],{"class":1436},"sender ",[1086,80171,1440],{"class":1146},[1086,80173,79990],{"class":1436},[1086,80175,861],{"class":1146},[1086,80177,80075],{"class":1105},[1086,80179,1398],{"class":1146},[1086,80181,60749],{"class":1105},[1086,80183,1227],{"class":1146},[1086,80185,1195],{"class":1146},[1086,80187,80188],{"class":1096},"MessageSender",[1086,80190,1159],{"class":1146},[1086,80192,1455],{"class":1146},[1086,80194,80195,80197,80199,80201,80203,80206,80208,80210,80213,80215,80217,80219,80221,80223,80226],{"class":1088,"line":3421},[1086,80196,80096],{"class":1436},[1086,80198,861],{"class":1146},[1086,80200,80075],{"class":1105},[1086,80202,1398],{"class":1146},[1086,80204,80205],{"class":1105},"sender",[1086,80207,1227],{"class":1146},[1086,80209,1195],{"class":1146},[1086,80211,80212],{"class":1096},"PartyId",[1086,80214,1159],{"class":1146},[1086,80216,6786],{"class":1146},[1086,80218,1018],{"class":4109},[1086,80220,19552],{"class":1146},[1086,80222,1195],{"class":1146},[1086,80224,80225],{"class":1096},"PADPIDA2023010101A",[1086,80227,4441],{"class":1146},[1086,80229,80230,80232,80234,80236,80238,80240,80242,80244,80246,80248,80250,80252,80254,80256,80259],{"class":1088,"line":3427},[1086,80231,80096],{"class":1436},[1086,80233,861],{"class":1146},[1086,80235,80075],{"class":1105},[1086,80237,1398],{"class":1146},[1086,80239,80205],{"class":1105},[1086,80241,1227],{"class":1146},[1086,80243,1195],{"class":1146},[1086,80245,17933],{"class":1096},[1086,80247,1159],{"class":1146},[1086,80249,6786],{"class":1146},[1086,80251,1018],{"class":4109},[1086,80253,19552],{"class":1146},[1086,80255,1195],{"class":1146},[1086,80257,80258],{"class":1096},"My Label",[1086,80260,4441],{"class":1146},[1086,80262,80263],{"class":1088,"line":3433},[1086,80264,3390],{"emptyLinePlaceholder":738},[1086,80266,80267,80270,80272,80274,80276,80278,80280,80282,80284,80286,80289,80291],{"class":1088,"line":3439},[1086,80268,80269],{"class":1436},"recipient ",[1086,80271,1440],{"class":1146},[1086,80273,79990],{"class":1436},[1086,80275,861],{"class":1146},[1086,80277,80075],{"class":1105},[1086,80279,1398],{"class":1146},[1086,80281,60749],{"class":1105},[1086,80283,1227],{"class":1146},[1086,80285,1195],{"class":1146},[1086,80287,80288],{"class":1096},"MessageRecipient",[1086,80290,1159],{"class":1146},[1086,80292,1455],{"class":1146},[1086,80294,80295,80297,80299,80301,80303,80306,80308,80310,80312,80314,80316,80318,80320,80322,80325],{"class":1088,"line":3444},[1086,80296,80096],{"class":1436},[1086,80298,861],{"class":1146},[1086,80300,80075],{"class":1105},[1086,80302,1398],{"class":1146},[1086,80304,80305],{"class":1105},"recipient",[1086,80307,1227],{"class":1146},[1086,80309,1195],{"class":1146},[1086,80311,80212],{"class":1096},[1086,80313,1159],{"class":1146},[1086,80315,6786],{"class":1146},[1086,80317,1018],{"class":4109},[1086,80319,19552],{"class":1146},[1086,80321,1195],{"class":1146},[1086,80323,80324],{"class":1096},"PADPIDA2020121501Y",[1086,80326,4441],{"class":1146},[1086,80328,80329,80331,80333,80335,80337,80339,80341,80343,80345,80347,80349,80351,80353,80355,80357],{"class":1088,"line":3450},[1086,80330,80096],{"class":1436},[1086,80332,861],{"class":1146},[1086,80334,80075],{"class":1105},[1086,80336,1398],{"class":1146},[1086,80338,80305],{"class":1105},[1086,80340,1227],{"class":1146},[1086,80342,1195],{"class":1146},[1086,80344,17933],{"class":1096},[1086,80346,1159],{"class":1146},[1086,80348,6786],{"class":1146},[1086,80350,1018],{"class":4109},[1086,80352,19552],{"class":1146},[1086,80354,1195],{"class":1146},[1086,80356,58345],{"class":1096},[1086,80358,4441],{"class":1146},[1086,80360,80361],{"class":1088,"line":3456},[1086,80362,3390],{"emptyLinePlaceholder":738},[1086,80364,80365],{"class":1088,"line":3462},[1086,80366,80367],{"class":1427},"# Resource list\n",[1086,80369,80370,80373,80375,80377,80379,80381,80383,80385,80387,80389,80391,80393],{"class":1088,"line":3467},[1086,80371,80372],{"class":1436},"resource_list ",[1086,80374,1440],{"class":1146},[1086,80376,79990],{"class":1436},[1086,80378,861],{"class":1146},[1086,80380,80075],{"class":1105},[1086,80382,1398],{"class":1146},[1086,80384,80080],{"class":1105},[1086,80386,1227],{"class":1146},[1086,80388,1195],{"class":1146},[1086,80390,17780],{"class":1096},[1086,80392,1159],{"class":1146},[1086,80394,1455],{"class":1146},[1086,80396,80397,80400,80402,80404,80406,80408,80410,80413,80415,80417,80419,80421],{"class":1088,"line":3473},[1086,80398,80399],{"class":1436},"sound_recording ",[1086,80401,1440],{"class":1146},[1086,80403,79990],{"class":1436},[1086,80405,861],{"class":1146},[1086,80407,80075],{"class":1105},[1086,80409,1398],{"class":1146},[1086,80411,80412],{"class":1105},"resource_list",[1086,80414,1227],{"class":1146},[1086,80416,1195],{"class":1146},[1086,80418,11280],{"class":1096},[1086,80420,1159],{"class":1146},[1086,80422,1455],{"class":1146},[1086,80424,80425,80427,80429,80431,80433,80436,80438,80440,80442,80444,80446,80448,80450,80452,80454],{"class":1088,"line":3479},[1086,80426,80096],{"class":1436},[1086,80428,861],{"class":1146},[1086,80430,80075],{"class":1105},[1086,80432,1398],{"class":1146},[1086,80434,80435],{"class":1105},"sound_recording",[1086,80437,1227],{"class":1146},[1086,80439,1195],{"class":1146},[1086,80441,16099],{"class":1096},[1086,80443,1159],{"class":1146},[1086,80445,6786],{"class":1146},[1086,80447,1018],{"class":4109},[1086,80449,19552],{"class":1146},[1086,80451,1195],{"class":1146},[1086,80453,16104],{"class":1096},[1086,80455,4441],{"class":1146},[1086,80457,80458],{"class":1088,"line":3485},[1086,80459,3390],{"emptyLinePlaceholder":738},[1086,80461,80462,80465,80467,80469,80471,80473,80475,80477,80479,80481,80483,80485],{"class":1088,"line":3491},[1086,80463,80464],{"class":1436},"details ",[1086,80466,1440],{"class":1146},[1086,80468,79990],{"class":1436},[1086,80470,861],{"class":1146},[1086,80472,80075],{"class":1105},[1086,80474,1398],{"class":1146},[1086,80476,80435],{"class":1105},[1086,80478,1227],{"class":1146},[1086,80480,1195],{"class":1146},[1086,80482,17852],{"class":1096},[1086,80484,1159],{"class":1146},[1086,80486,1455],{"class":1146},[1086,80488,80489,80492,80494,80496,80498,80500,80502,80504,80506,80508,80510,80512],{"class":1088,"line":3497},[1086,80490,80491],{"class":1436},"title ",[1086,80493,1440],{"class":1146},[1086,80495,79990],{"class":1436},[1086,80497,861],{"class":1146},[1086,80499,80075],{"class":1105},[1086,80501,1398],{"class":1146},[1086,80503,8186],{"class":1105},[1086,80505,1227],{"class":1146},[1086,80507,1195],{"class":1146},[1086,80509,8859],{"class":1096},[1086,80511,1159],{"class":1146},[1086,80513,1455],{"class":1146},[1086,80515,80516,80518,80520,80522,80524,80526,80528,80530,80532,80534,80536,80538,80540,80542,80545],{"class":1088,"line":3503},[1086,80517,80096],{"class":1436},[1086,80519,861],{"class":1146},[1086,80521,80075],{"class":1105},[1086,80523,1398],{"class":1146},[1086,80525,9069],{"class":1105},[1086,80527,1227],{"class":1146},[1086,80529,1195],{"class":1146},[1086,80531,17898],{"class":1096},[1086,80533,1159],{"class":1146},[1086,80535,6786],{"class":1146},[1086,80537,1018],{"class":4109},[1086,80539,19552],{"class":1146},[1086,80541,1195],{"class":1146},[1086,80543,80544],{"class":1096},"My Song Title",[1086,80546,4441],{"class":1146},[1086,80548,80549,80551,80553,80555,80557,80559,80561,80563,80565,80567,80569,80571,80573,80575,80578],{"class":1088,"line":3509},[1086,80550,80096],{"class":1436},[1086,80552,861],{"class":1146},[1086,80554,80075],{"class":1105},[1086,80556,1398],{"class":1146},[1086,80558,8186],{"class":1105},[1086,80560,1227],{"class":1146},[1086,80562,1195],{"class":1146},[1086,80564,11305],{"class":1096},[1086,80566,1159],{"class":1146},[1086,80568,6786],{"class":1146},[1086,80570,1018],{"class":4109},[1086,80572,19552],{"class":1146},[1086,80574,1195],{"class":1146},[1086,80576,80577],{"class":1096},"PT3M45S",[1086,80579,4441],{"class":1146},[1086,80581,80582],{"class":1088,"line":3515},[1086,80583,3390],{"emptyLinePlaceholder":738},[1086,80585,80586],{"class":1088,"line":3520},[1086,80587,80588],{"class":1427},"# Write to file\n",[1086,80590,80591,80594,80596,80598,80600,80602,80604,80606],{"class":1088,"line":3526},[1086,80592,80593],{"class":1436},"tree ",[1086,80595,1440],{"class":1146},[1086,80597,79990],{"class":1436},[1086,80599,861],{"class":1146},[1086,80601,79966],{"class":1105},[1086,80603,1398],{"class":1146},[1086,80605,80080],{"class":1105},[1086,80607,1455],{"class":1146},[1086,80609,80610,80612,80614,80617,80619,80622,80624,80627,80629,80631,80633],{"class":1088,"line":3531},[1086,80611,80096],{"class":1436},[1086,80613,861],{"class":1146},[1086,80615,80616],{"class":1105},"indent",[1086,80618,1398],{"class":1146},[1086,80620,80621],{"class":1105},"tree",[1086,80623,1227],{"class":1146},[1086,80625,80626],{"class":1401}," space",[1086,80628,1440],{"class":1146},[1086,80630,1159],{"class":1146},[1086,80632,1152],{"class":1146},[1086,80634,1455],{"class":1146},[1086,80636,80637,80639,80641,80643,80645,80647,80650,80652,80654,80657,80659,80661,80663,80665,80668,80670],{"class":1088,"line":3537},[1086,80638,80621],{"class":1436},[1086,80640,861],{"class":1146},[1086,80642,29046],{"class":1105},[1086,80644,1398],{"class":1146},[1086,80646,1159],{"class":1146},[1086,80648,80649],{"class":1096},"release.xml",[1086,80651,1159],{"class":1146},[1086,80653,1227],{"class":1146},[1086,80655,80656],{"class":1401}," xml_declaration",[1086,80658,22246],{"class":1146},[1086,80660,29021],{"class":1401},[1086,80662,1440],{"class":1146},[1086,80664,1159],{"class":1146},[1086,80666,80667],{"class":1096},"UTF-8",[1086,80669,1159],{"class":1146},[1086,80671,1455],{"class":1146},[1086,80673,80674],{"class":1088,"line":3543},[1086,80675,3390],{"emptyLinePlaceholder":738},[1086,80677,80678,80680,80682,80684,80687,80689],{"class":1088,"line":3549},[1086,80679,10725],{"class":1105},[1086,80681,1398],{"class":1146},[1086,80683,1159],{"class":1146},[1086,80685,80686],{"class":1096},"DDEX ERN file generated: release.xml",[1086,80688,1159],{"class":1146},[1086,80690,1455],{"class":1146},[1074,80692,80694],{"id":80693},"more-complete-example-with-artists-and-deal-terms","More Complete Example: With Artists and Deal Terms",[842,80696,80697],{},"A real-world DDEX file includes artist details, ISRC codes, and deal terms. Here's a more complete version:",[1013,80699,80701],{"className":1368,"code":80700,"language":1250,"meta":728,"style":728},"import xml.etree.ElementTree as ET\nfrom datetime import datetime\n\n\ndef create_ern_release(track_data: dict, sender: dict, recipient: dict) -> ET.Element:\n    \"\"\"Generate a DDEX ERN 4.3 NewReleaseMessage.\"\"\"\n    root = ET.Element(\"ern:NewReleaseMessage\", {\n        \"xmlns:ern\": \"http://ddex.net/xml/ern/43\",\n        \"MessageSchemaVersionId\": \"ern/43\",\n    })\n\n    # Header\n    header = ET.SubElement(root, \"MessageHeader\")\n    ET.SubElement(header, \"MessageId\").text = f\"MSG-{datetime.now():%Y%m%d%H%M%S}\"\n    ET.SubElement(header, \"MessageCreatedDateTime\").text = datetime.now().isoformat()\n\n    msg_sender = ET.SubElement(header, \"MessageSender\")\n    ET.SubElement(msg_sender, \"PartyId\").text = sender[\"dpid\"]\n    ET.SubElement(msg_sender, \"PartyName\").text = sender[\"name\"]\n\n    msg_recipient = ET.SubElement(header, \"MessageRecipient\")\n    ET.SubElement(msg_recipient, \"PartyId\").text = recipient[\"dpid\"]\n    ET.SubElement(msg_recipient, \"PartyName\").text = recipient[\"name\"]\n\n    # Resource list\n    resource_list = ET.SubElement(root, \"ResourceList\")\n    recording = ET.SubElement(resource_list, \"SoundRecording\")\n    ET.SubElement(recording, \"ResourceReference\").text = \"A1\"\n\n    sr_id = ET.SubElement(recording, \"SoundRecordingId\")\n    ET.SubElement(sr_id, \"ISRC\").text = track_data[\"isrc\"]\n\n    details = ET.SubElement(recording, \"SoundRecordingDetailsByTerritory\")\n    title_el = ET.SubElement(details, \"Title\")\n    ET.SubElement(title_el, \"TitleText\").text = track_data[\"title\"]\n    ET.SubElement(details, \"Duration\").text = track_data[\"duration\"]\n\n    # Artists\n    for artist in track_data.get(\"artists\", []):\n        contributor = ET.SubElement(details, \"DisplayArtist\")\n        name_el = ET.SubElement(contributor, \"PartyName\")\n        ET.SubElement(name_el, \"FullName\").text = artist[\"name\"]\n        ET.SubElement(contributor, \"ArtistRole\").text = artist.get(\"role\", \"MainArtist\")\n\n    # Release list\n    release_list = ET.SubElement(root, \"ReleaseList\")\n    release = ET.SubElement(release_list, \"Release\")\n    release_id = ET.SubElement(release, \"ReleaseId\")\n    ET.SubElement(release_id, \"ICPN\").text = track_data.get(\"upc\", \"\")\n\n    release_detail = ET.SubElement(release, \"ReleaseDetailsByTerritory\")\n    ET.SubElement(release_detail, \"TerritoryCode\").text = \"Worldwide\"\n\n    # Deal list\n    deal_list = ET.SubElement(root, \"DealList\")\n    deal = ET.SubElement(deal_list, \"ReleaseDeal\")\n    deal_terms = ET.SubElement(deal, \"Deal\")\n    ET.SubElement(deal_terms, \"CommercialModelType\").text = \"SubscriptionModel\"\n    ET.SubElement(deal_terms, \"Usage\").text = \"OnDemandStream\"\n\n    validity = ET.SubElement(deal_terms, \"ValidityPeriod\")\n    ET.SubElement(validity, \"StartDate\").text = track_data.get(\"release_date\", \"\")\n\n    return root\n\n\n# Usage\ntrack = {\n    \"title\": \"Ocean Waves\",\n    \"isrc\": \"USRC12345678\",\n    \"duration\": \"PT4M12S\",\n    \"upc\": \"0123456789012\",\n    \"release_date\": \"2024-01-15\",\n    \"artists\": [\n        {\"name\": \"Jane Doe\", \"role\": \"MainArtist\"},\n        {\"name\": \"John Smith\", \"role\": \"FeaturedArtist\"},\n    ],\n}\n\nern = create_ern_release(\n    track_data=track,\n    sender={\"dpid\": \"PADPIDA2023010101A\", \"name\": \"My Label\"},\n    recipient={\"dpid\": \"PADPIDA2020121501Y\", \"name\": \"YouTube\"},\n)\n\ntree = ET.ElementTree(ern)\nET.indent(tree, space=\"  \")\ntree.write(\"release_complete.xml\", xml_declaration=True, encoding=\"UTF-8\")\nprint(\"Complete DDEX ERN file generated.\")\n",[895,80702,80703,80721,80732,80736,80740,80786,80795,80820,80838,80856,80861,80865,80870,80897,80972,81012,81016,81043,81083,81121,81125,81152,81191,81229,81233,81238,81265,81292,81325,81329,81356,81396,81400,81427,81454,81493,81531,81535,81540,81566,81593,81621,81662,81712,81716,81721,81748,81776,81804,81851,81855,81882,81915,81919,81924,81951,81979,82007,82040,82072,82076,82103,82151,82155,82162,82166,82170,82175,82184,82203,82222,82241,82259,82278,82290,82327,82365,82369,82373,82377,82388,82399,82438,82477,82481,82485,82504,82528,82563],{"__ignoreMap":728},[1086,80704,80705,80707,80709,80711,80713,80715,80717,80719],{"class":1088,"line":1089},[1086,80706,6503],{"class":1423},[1086,80708,79956],{"class":1436},[1086,80710,861],{"class":1146},[1086,80712,79961],{"class":4109},[1086,80714,861],{"class":1146},[1086,80716,79966],{"class":4109},[1086,80718,19873],{"class":1423},[1086,80720,79971],{"class":1436},[1086,80722,80723,80725,80727,80729],{"class":1088,"line":729},[1086,80724,15570],{"class":1423},[1086,80726,60026],{"class":1436},[1086,80728,6503],{"class":1423},[1086,80730,80731],{"class":1436}," datetime\n",[1086,80733,80734],{"class":1088,"line":1112},[1086,80735,3390],{"emptyLinePlaceholder":738},[1086,80737,80738],{"class":1088,"line":1181},[1086,80739,3390],{"emptyLinePlaceholder":738},[1086,80741,80742,80744,80747,80749,80752,80754,80756,80758,80761,80763,80765,80767,80770,80772,80774,80776,80778,80780,80782,80784],{"class":1088,"line":1205},[1086,80743,1392],{"class":1155},[1086,80745,80746],{"class":1105}," create_ern_release",[1086,80748,1398],{"class":1146},[1086,80750,80751],{"class":1401},"track_data",[1086,80753,1133],{"class":1146},[1086,80755,1518],{"class":1092},[1086,80757,1227],{"class":1146},[1086,80759,80760],{"class":1401}," sender",[1086,80762,1133],{"class":1146},[1086,80764,1518],{"class":1092},[1086,80766,1227],{"class":1146},[1086,80768,80769],{"class":1401}," recipient",[1086,80771,1133],{"class":1146},[1086,80773,1518],{"class":1092},[1086,80775,1410],{"class":1146},[1086,80777,1413],{"class":1146},[1086,80779,79990],{"class":1436},[1086,80781,861],{"class":1146},[1086,80783,79995],{"class":4109},[1086,80785,1418],{"class":1146},[1086,80787,80788,80790,80793],{"class":1088,"line":1276},[1086,80789,1424],{"class":1423},[1086,80791,80792],{"class":1427},"Generate a DDEX ERN 4.3 NewReleaseMessage.",[1086,80794,1431],{"class":1423},[1086,80796,80797,80800,80802,80804,80806,80808,80810,80812,80814,80816,80818],{"class":1088,"line":1282},[1086,80798,80799],{"class":1436},"    root ",[1086,80801,1440],{"class":1146},[1086,80803,79990],{"class":1436},[1086,80805,861],{"class":1146},[1086,80807,79995],{"class":1105},[1086,80809,1398],{"class":1146},[1086,80811,1159],{"class":1146},[1086,80813,80002],{"class":1096},[1086,80815,1159],{"class":1146},[1086,80817,1227],{"class":1146},[1086,80819,1164],{"class":1146},[1086,80821,80822,80824,80826,80828,80830,80832,80834,80836],{"class":1088,"line":1288},[1086,80823,6046],{"class":1146},[1086,80825,80015],{"class":1096},[1086,80827,1159],{"class":1146},[1086,80829,1133],{"class":1146},[1086,80831,1195],{"class":1146},[1086,80833,80024],{"class":1096},[1086,80835,1159],{"class":1146},[1086,80837,1202],{"class":1146},[1086,80839,80840,80842,80844,80846,80848,80850,80852,80854],{"class":1088,"line":2685},[1086,80841,6046],{"class":1146},[1086,80843,80035],{"class":1096},[1086,80845,1159],{"class":1146},[1086,80847,1133],{"class":1146},[1086,80849,1195],{"class":1146},[1086,80851,80044],{"class":1096},[1086,80853,1159],{"class":1146},[1086,80855,1202],{"class":1146},[1086,80857,80858],{"class":1088,"line":2700},[1086,80859,80860],{"class":1146},"    })\n",[1086,80862,80863],{"class":1088,"line":3398},[1086,80864,3390],{"emptyLinePlaceholder":738},[1086,80866,80867],{"class":1088,"line":1715},[1086,80868,80869],{"class":1427},"    # Header\n",[1086,80871,80872,80875,80877,80879,80881,80883,80885,80887,80889,80891,80893,80895],{"class":1088,"line":3409},[1086,80873,80874],{"class":1436},"    header ",[1086,80876,1440],{"class":1146},[1086,80878,79990],{"class":1436},[1086,80880,861],{"class":1146},[1086,80882,80075],{"class":1105},[1086,80884,1398],{"class":1146},[1086,80886,80080],{"class":1105},[1086,80888,1227],{"class":1146},[1086,80890,1195],{"class":1146},[1086,80892,80087],{"class":1096},[1086,80894,1159],{"class":1146},[1086,80896,1455],{"class":1146},[1086,80898,80899,80902,80904,80906,80908,80910,80912,80914,80916,80918,80920,80922,80924,80926,80929,80931,80934,80936,80938,80941,80944,80946,80949,80951,80953,80955,80958,80960,80963,80965,80968,80970],{"class":1088,"line":3415},[1086,80900,80901],{"class":1436},"    ET",[1086,80903,861],{"class":1146},[1086,80905,80075],{"class":1105},[1086,80907,1398],{"class":1146},[1086,80909,60749],{"class":1105},[1086,80911,1227],{"class":1146},[1086,80913,1195],{"class":1146},[1086,80915,80111],{"class":1096},[1086,80917,1159],{"class":1146},[1086,80919,6786],{"class":1146},[1086,80921,1018],{"class":4109},[1086,80923,19552],{"class":1146},[1086,80925,4403],{"class":1155},[1086,80927,80928],{"class":1096},"\"MSG-",[1086,80930,4409],{"class":1187},[1086,80932,80933],{"class":1436},"datetime",[1086,80935,861],{"class":1146},[1086,80937,23189],{"class":1105},[1086,80939,80940],{"class":1146},"():%",[1086,80942,80943],{"class":1436},"Y",[1086,80945,62326],{"class":1146},[1086,80947,80948],{"class":1436},"m",[1086,80950,62326],{"class":1146},[1086,80952,48055],{"class":1436},[1086,80954,62326],{"class":1146},[1086,80956,80957],{"class":1436},"H",[1086,80959,62326],{"class":1146},[1086,80961,80962],{"class":1436},"M",[1086,80964,62326],{"class":1146},[1086,80966,80967],{"class":1436},"S",[1086,80969,4423],{"class":1187},[1086,80971,4441],{"class":1096},[1086,80973,80974,80976,80978,80980,80982,80984,80986,80988,80990,80992,80994,80996,80998,81001,81003,81005,81007,81010],{"class":1088,"line":3421},[1086,80975,80901],{"class":1436},[1086,80977,861],{"class":1146},[1086,80979,80075],{"class":1105},[1086,80981,1398],{"class":1146},[1086,80983,60749],{"class":1105},[1086,80985,1227],{"class":1146},[1086,80987,1195],{"class":1146},[1086,80989,80145],{"class":1096},[1086,80991,1159],{"class":1146},[1086,80993,6786],{"class":1146},[1086,80995,1018],{"class":4109},[1086,80997,19552],{"class":1146},[1086,80999,81000],{"class":1436}," datetime",[1086,81002,861],{"class":1146},[1086,81004,23189],{"class":1105},[1086,81006,6813],{"class":1146},[1086,81008,81009],{"class":1105},"isoformat",[1086,81011,1387],{"class":1146},[1086,81013,81014],{"class":1088,"line":3427},[1086,81015,3390],{"emptyLinePlaceholder":738},[1086,81017,81018,81021,81023,81025,81027,81029,81031,81033,81035,81037,81039,81041],{"class":1088,"line":3433},[1086,81019,81020],{"class":1436},"    msg_sender ",[1086,81022,1440],{"class":1146},[1086,81024,79990],{"class":1436},[1086,81026,861],{"class":1146},[1086,81028,80075],{"class":1105},[1086,81030,1398],{"class":1146},[1086,81032,60749],{"class":1105},[1086,81034,1227],{"class":1146},[1086,81036,1195],{"class":1146},[1086,81038,80188],{"class":1096},[1086,81040,1159],{"class":1146},[1086,81042,1455],{"class":1146},[1086,81044,81045,81047,81049,81051,81053,81056,81058,81060,81062,81064,81066,81068,81070,81072,81074,81076,81079,81081],{"class":1088,"line":3439},[1086,81046,80901],{"class":1436},[1086,81048,861],{"class":1146},[1086,81050,80075],{"class":1105},[1086,81052,1398],{"class":1146},[1086,81054,81055],{"class":1105},"msg_sender",[1086,81057,1227],{"class":1146},[1086,81059,1195],{"class":1146},[1086,81061,80212],{"class":1096},[1086,81063,1159],{"class":1146},[1086,81065,6786],{"class":1146},[1086,81067,1018],{"class":4109},[1086,81069,19552],{"class":1146},[1086,81071,80760],{"class":1436},[1086,81073,4340],{"class":1146},[1086,81075,1159],{"class":1146},[1086,81077,81078],{"class":1096},"dpid",[1086,81080,1159],{"class":1146},[1086,81082,1273],{"class":1146},[1086,81084,81085,81087,81089,81091,81093,81095,81097,81099,81101,81103,81105,81107,81109,81111,81113,81115,81117,81119],{"class":1088,"line":3444},[1086,81086,80901],{"class":1436},[1086,81088,861],{"class":1146},[1086,81090,80075],{"class":1105},[1086,81092,1398],{"class":1146},[1086,81094,81055],{"class":1105},[1086,81096,1227],{"class":1146},[1086,81098,1195],{"class":1146},[1086,81100,17933],{"class":1096},[1086,81102,1159],{"class":1146},[1086,81104,6786],{"class":1146},[1086,81106,1018],{"class":4109},[1086,81108,19552],{"class":1146},[1086,81110,80760],{"class":1436},[1086,81112,4340],{"class":1146},[1086,81114,1159],{"class":1146},[1086,81116,4184],{"class":1096},[1086,81118,1159],{"class":1146},[1086,81120,1273],{"class":1146},[1086,81122,81123],{"class":1088,"line":3450},[1086,81124,3390],{"emptyLinePlaceholder":738},[1086,81126,81127,81130,81132,81134,81136,81138,81140,81142,81144,81146,81148,81150],{"class":1088,"line":3456},[1086,81128,81129],{"class":1436},"    msg_recipient ",[1086,81131,1440],{"class":1146},[1086,81133,79990],{"class":1436},[1086,81135,861],{"class":1146},[1086,81137,80075],{"class":1105},[1086,81139,1398],{"class":1146},[1086,81141,60749],{"class":1105},[1086,81143,1227],{"class":1146},[1086,81145,1195],{"class":1146},[1086,81147,80288],{"class":1096},[1086,81149,1159],{"class":1146},[1086,81151,1455],{"class":1146},[1086,81153,81154,81156,81158,81160,81162,81165,81167,81169,81171,81173,81175,81177,81179,81181,81183,81185,81187,81189],{"class":1088,"line":3462},[1086,81155,80901],{"class":1436},[1086,81157,861],{"class":1146},[1086,81159,80075],{"class":1105},[1086,81161,1398],{"class":1146},[1086,81163,81164],{"class":1105},"msg_recipient",[1086,81166,1227],{"class":1146},[1086,81168,1195],{"class":1146},[1086,81170,80212],{"class":1096},[1086,81172,1159],{"class":1146},[1086,81174,6786],{"class":1146},[1086,81176,1018],{"class":4109},[1086,81178,19552],{"class":1146},[1086,81180,80769],{"class":1436},[1086,81182,4340],{"class":1146},[1086,81184,1159],{"class":1146},[1086,81186,81078],{"class":1096},[1086,81188,1159],{"class":1146},[1086,81190,1273],{"class":1146},[1086,81192,81193,81195,81197,81199,81201,81203,81205,81207,81209,81211,81213,81215,81217,81219,81221,81223,81225,81227],{"class":1088,"line":3467},[1086,81194,80901],{"class":1436},[1086,81196,861],{"class":1146},[1086,81198,80075],{"class":1105},[1086,81200,1398],{"class":1146},[1086,81202,81164],{"class":1105},[1086,81204,1227],{"class":1146},[1086,81206,1195],{"class":1146},[1086,81208,17933],{"class":1096},[1086,81210,1159],{"class":1146},[1086,81212,6786],{"class":1146},[1086,81214,1018],{"class":4109},[1086,81216,19552],{"class":1146},[1086,81218,80769],{"class":1436},[1086,81220,4340],{"class":1146},[1086,81222,1159],{"class":1146},[1086,81224,4184],{"class":1096},[1086,81226,1159],{"class":1146},[1086,81228,1273],{"class":1146},[1086,81230,81231],{"class":1088,"line":3473},[1086,81232,3390],{"emptyLinePlaceholder":738},[1086,81234,81235],{"class":1088,"line":3479},[1086,81236,81237],{"class":1427},"    # Resource list\n",[1086,81239,81240,81243,81245,81247,81249,81251,81253,81255,81257,81259,81261,81263],{"class":1088,"line":3485},[1086,81241,81242],{"class":1436},"    resource_list ",[1086,81244,1440],{"class":1146},[1086,81246,79990],{"class":1436},[1086,81248,861],{"class":1146},[1086,81250,80075],{"class":1105},[1086,81252,1398],{"class":1146},[1086,81254,80080],{"class":1105},[1086,81256,1227],{"class":1146},[1086,81258,1195],{"class":1146},[1086,81260,17780],{"class":1096},[1086,81262,1159],{"class":1146},[1086,81264,1455],{"class":1146},[1086,81266,81267,81270,81272,81274,81276,81278,81280,81282,81284,81286,81288,81290],{"class":1088,"line":3491},[1086,81268,81269],{"class":1436},"    recording ",[1086,81271,1440],{"class":1146},[1086,81273,79990],{"class":1436},[1086,81275,861],{"class":1146},[1086,81277,80075],{"class":1105},[1086,81279,1398],{"class":1146},[1086,81281,80412],{"class":1105},[1086,81283,1227],{"class":1146},[1086,81285,1195],{"class":1146},[1086,81287,11280],{"class":1096},[1086,81289,1159],{"class":1146},[1086,81291,1455],{"class":1146},[1086,81293,81294,81296,81298,81300,81302,81305,81307,81309,81311,81313,81315,81317,81319,81321,81323],{"class":1088,"line":3497},[1086,81295,80901],{"class":1436},[1086,81297,861],{"class":1146},[1086,81299,80075],{"class":1105},[1086,81301,1398],{"class":1146},[1086,81303,81304],{"class":1105},"recording",[1086,81306,1227],{"class":1146},[1086,81308,1195],{"class":1146},[1086,81310,16099],{"class":1096},[1086,81312,1159],{"class":1146},[1086,81314,6786],{"class":1146},[1086,81316,1018],{"class":4109},[1086,81318,19552],{"class":1146},[1086,81320,1195],{"class":1146},[1086,81322,16104],{"class":1096},[1086,81324,4441],{"class":1146},[1086,81326,81327],{"class":1088,"line":3503},[1086,81328,3390],{"emptyLinePlaceholder":738},[1086,81330,81331,81334,81336,81338,81340,81342,81344,81346,81348,81350,81352,81354],{"class":1088,"line":3509},[1086,81332,81333],{"class":1436},"    sr_id ",[1086,81335,1440],{"class":1146},[1086,81337,79990],{"class":1436},[1086,81339,861],{"class":1146},[1086,81341,80075],{"class":1105},[1086,81343,1398],{"class":1146},[1086,81345,81304],{"class":1105},[1086,81347,1227],{"class":1146},[1086,81349,1195],{"class":1146},[1086,81351,16266],{"class":1096},[1086,81353,1159],{"class":1146},[1086,81355,1455],{"class":1146},[1086,81357,81358,81360,81362,81364,81366,81369,81371,81373,81375,81377,81379,81381,81383,81386,81388,81390,81392,81394],{"class":1088,"line":3515},[1086,81359,80901],{"class":1436},[1086,81361,861],{"class":1146},[1086,81363,80075],{"class":1105},[1086,81365,1398],{"class":1146},[1086,81367,81368],{"class":1105},"sr_id",[1086,81370,1227],{"class":1146},[1086,81372,1195],{"class":1146},[1086,81374,3856],{"class":1096},[1086,81376,1159],{"class":1146},[1086,81378,6786],{"class":1146},[1086,81380,1018],{"class":4109},[1086,81382,19552],{"class":1146},[1086,81384,81385],{"class":1436}," track_data",[1086,81387,4340],{"class":1146},[1086,81389,1159],{"class":1146},[1086,81391,1402],{"class":1096},[1086,81393,1159],{"class":1146},[1086,81395,1273],{"class":1146},[1086,81397,81398],{"class":1088,"line":3520},[1086,81399,3390],{"emptyLinePlaceholder":738},[1086,81401,81402,81405,81407,81409,81411,81413,81415,81417,81419,81421,81423,81425],{"class":1088,"line":3526},[1086,81403,81404],{"class":1436},"    details ",[1086,81406,1440],{"class":1146},[1086,81408,79990],{"class":1436},[1086,81410,861],{"class":1146},[1086,81412,80075],{"class":1105},[1086,81414,1398],{"class":1146},[1086,81416,81304],{"class":1105},[1086,81418,1227],{"class":1146},[1086,81420,1195],{"class":1146},[1086,81422,17852],{"class":1096},[1086,81424,1159],{"class":1146},[1086,81426,1455],{"class":1146},[1086,81428,81429,81432,81434,81436,81438,81440,81442,81444,81446,81448,81450,81452],{"class":1088,"line":3531},[1086,81430,81431],{"class":1436},"    title_el ",[1086,81433,1440],{"class":1146},[1086,81435,79990],{"class":1436},[1086,81437,861],{"class":1146},[1086,81439,80075],{"class":1105},[1086,81441,1398],{"class":1146},[1086,81443,8186],{"class":1105},[1086,81445,1227],{"class":1146},[1086,81447,1195],{"class":1146},[1086,81449,8859],{"class":1096},[1086,81451,1159],{"class":1146},[1086,81453,1455],{"class":1146},[1086,81455,81456,81458,81460,81462,81464,81467,81469,81471,81473,81475,81477,81479,81481,81483,81485,81487,81489,81491],{"class":1088,"line":3537},[1086,81457,80901],{"class":1436},[1086,81459,861],{"class":1146},[1086,81461,80075],{"class":1105},[1086,81463,1398],{"class":1146},[1086,81465,81466],{"class":1105},"title_el",[1086,81468,1227],{"class":1146},[1086,81470,1195],{"class":1146},[1086,81472,17898],{"class":1096},[1086,81474,1159],{"class":1146},[1086,81476,6786],{"class":1146},[1086,81478,1018],{"class":4109},[1086,81480,19552],{"class":1146},[1086,81482,81385],{"class":1436},[1086,81484,4340],{"class":1146},[1086,81486,1159],{"class":1146},[1086,81488,9069],{"class":1096},[1086,81490,1159],{"class":1146},[1086,81492,1273],{"class":1146},[1086,81494,81495,81497,81499,81501,81503,81505,81507,81509,81511,81513,81515,81517,81519,81521,81523,81525,81527,81529],{"class":1088,"line":3543},[1086,81496,80901],{"class":1436},[1086,81498,861],{"class":1146},[1086,81500,80075],{"class":1105},[1086,81502,1398],{"class":1146},[1086,81504,8186],{"class":1105},[1086,81506,1227],{"class":1146},[1086,81508,1195],{"class":1146},[1086,81510,11305],{"class":1096},[1086,81512,1159],{"class":1146},[1086,81514,6786],{"class":1146},[1086,81516,1018],{"class":4109},[1086,81518,19552],{"class":1146},[1086,81520,81385],{"class":1436},[1086,81522,4340],{"class":1146},[1086,81524,1159],{"class":1146},[1086,81526,30984],{"class":1096},[1086,81528,1159],{"class":1146},[1086,81530,1273],{"class":1146},[1086,81532,81533],{"class":1088,"line":3549},[1086,81534,3390],{"emptyLinePlaceholder":738},[1086,81536,81537],{"class":1088,"line":3555},[1086,81538,81539],{"class":1427},"    # Artists\n",[1086,81541,81542,81544,81546,81548,81550,81552,81554,81556,81558,81560,81562,81564],{"class":1088,"line":3561},[1086,81543,5925],{"class":1423},[1086,81545,6264],{"class":1436},[1086,81547,5931],{"class":1423},[1086,81549,81385],{"class":1436},[1086,81551,861],{"class":1146},[1086,81553,10812],{"class":1105},[1086,81555,1398],{"class":1146},[1086,81557,1159],{"class":1146},[1086,81559,17329],{"class":1096},[1086,81561,1159],{"class":1146},[1086,81563,1227],{"class":1146},[1086,81565,10826],{"class":1146},[1086,81567,81568,81571,81573,81575,81577,81579,81581,81583,81585,81587,81589,81591],{"class":1088,"line":3567},[1086,81569,81570],{"class":1436},"        contributor ",[1086,81572,1440],{"class":1146},[1086,81574,79990],{"class":1436},[1086,81576,861],{"class":1146},[1086,81578,80075],{"class":1105},[1086,81580,1398],{"class":1146},[1086,81582,8186],{"class":1105},[1086,81584,1227],{"class":1146},[1086,81586,1195],{"class":1146},[1086,81588,10500],{"class":1096},[1086,81590,1159],{"class":1146},[1086,81592,1455],{"class":1146},[1086,81594,81595,81598,81600,81602,81604,81606,81608,81611,81613,81615,81617,81619],{"class":1088,"line":17749},[1086,81596,81597],{"class":1436},"        name_el ",[1086,81599,1440],{"class":1146},[1086,81601,79990],{"class":1436},[1086,81603,861],{"class":1146},[1086,81605,80075],{"class":1105},[1086,81607,1398],{"class":1146},[1086,81609,81610],{"class":1105},"contributor",[1086,81612,1227],{"class":1146},[1086,81614,1195],{"class":1146},[1086,81616,17933],{"class":1096},[1086,81618,1159],{"class":1146},[1086,81620,1455],{"class":1146},[1086,81622,81623,81626,81628,81630,81632,81635,81637,81639,81641,81643,81645,81647,81649,81652,81654,81656,81658,81660],{"class":1088,"line":19843},[1086,81624,81625],{"class":1436},"        ET",[1086,81627,861],{"class":1146},[1086,81629,80075],{"class":1105},[1086,81631,1398],{"class":1146},[1086,81633,81634],{"class":1105},"name_el",[1086,81636,1227],{"class":1146},[1086,81638,1195],{"class":1146},[1086,81640,17939],{"class":1096},[1086,81642,1159],{"class":1146},[1086,81644,6786],{"class":1146},[1086,81646,1018],{"class":4109},[1086,81648,19552],{"class":1146},[1086,81650,81651],{"class":1436}," artist",[1086,81653,4340],{"class":1146},[1086,81655,1159],{"class":1146},[1086,81657,4184],{"class":1096},[1086,81659,1159],{"class":1146},[1086,81661,1273],{"class":1146},[1086,81663,81664,81666,81668,81670,81672,81674,81676,81678,81680,81682,81684,81686,81688,81690,81692,81694,81696,81698,81700,81702,81704,81706,81708,81710],{"class":1088,"line":19848},[1086,81665,81625],{"class":1436},[1086,81667,861],{"class":1146},[1086,81669,80075],{"class":1105},[1086,81671,1398],{"class":1146},[1086,81673,81610],{"class":1105},[1086,81675,1227],{"class":1146},[1086,81677,1195],{"class":1146},[1086,81679,11323],{"class":1096},[1086,81681,1159],{"class":1146},[1086,81683,6786],{"class":1146},[1086,81685,1018],{"class":4109},[1086,81687,19552],{"class":1146},[1086,81689,81651],{"class":1436},[1086,81691,861],{"class":1146},[1086,81693,10812],{"class":1105},[1086,81695,1398],{"class":1146},[1086,81697,1159],{"class":1146},[1086,81699,17362],{"class":1096},[1086,81701,1159],{"class":1146},[1086,81703,1227],{"class":1146},[1086,81705,1195],{"class":1146},[1086,81707,11328],{"class":1096},[1086,81709,1159],{"class":1146},[1086,81711,1455],{"class":1146},[1086,81713,81714],{"class":1088,"line":19880},[1086,81715,3390],{"emptyLinePlaceholder":738},[1086,81717,81718],{"class":1088,"line":19896},[1086,81719,81720],{"class":1427},"    # Release list\n",[1086,81722,81723,81726,81728,81730,81732,81734,81736,81738,81740,81742,81744,81746],{"class":1088,"line":19919},[1086,81724,81725],{"class":1436},"    release_list ",[1086,81727,1440],{"class":1146},[1086,81729,79990],{"class":1436},[1086,81731,861],{"class":1146},[1086,81733,80075],{"class":1105},[1086,81735,1398],{"class":1146},[1086,81737,80080],{"class":1105},[1086,81739,1227],{"class":1146},[1086,81741,1195],{"class":1146},[1086,81743,17998],{"class":1096},[1086,81745,1159],{"class":1146},[1086,81747,1455],{"class":1146},[1086,81749,81750,81753,81755,81757,81759,81761,81763,81766,81768,81770,81772,81774],{"class":1088,"line":19927},[1086,81751,81752],{"class":1436},"    release ",[1086,81754,1440],{"class":1146},[1086,81756,79990],{"class":1436},[1086,81758,861],{"class":1146},[1086,81760,80075],{"class":1105},[1086,81762,1398],{"class":1146},[1086,81764,81765],{"class":1105},"release_list",[1086,81767,1227],{"class":1146},[1086,81769,1195],{"class":1146},[1086,81771,11183],{"class":1096},[1086,81773,1159],{"class":1146},[1086,81775,1455],{"class":1146},[1086,81777,81778,81781,81783,81785,81787,81789,81791,81794,81796,81798,81800,81802],{"class":1088,"line":19948},[1086,81779,81780],{"class":1436},"    release_id ",[1086,81782,1440],{"class":1146},[1086,81784,79990],{"class":1436},[1086,81786,861],{"class":1146},[1086,81788,80075],{"class":1105},[1086,81790,1398],{"class":1146},[1086,81792,81793],{"class":1105},"release",[1086,81795,1227],{"class":1146},[1086,81797,1195],{"class":1146},[1086,81799,18021],{"class":1096},[1086,81801,1159],{"class":1146},[1086,81803,1455],{"class":1146},[1086,81805,81806,81808,81810,81812,81814,81816,81818,81820,81822,81824,81826,81828,81830,81832,81834,81836,81838,81840,81843,81845,81847,81849],{"class":1088,"line":19968},[1086,81807,80901],{"class":1436},[1086,81809,861],{"class":1146},[1086,81811,80075],{"class":1105},[1086,81813,1398],{"class":1146},[1086,81815,17638],{"class":1105},[1086,81817,1227],{"class":1146},[1086,81819,1195],{"class":1146},[1086,81821,11193],{"class":1096},[1086,81823,1159],{"class":1146},[1086,81825,6786],{"class":1146},[1086,81827,1018],{"class":4109},[1086,81829,19552],{"class":1146},[1086,81831,81385],{"class":1436},[1086,81833,861],{"class":1146},[1086,81835,10812],{"class":1105},[1086,81837,1398],{"class":1146},[1086,81839,1159],{"class":1146},[1086,81841,81842],{"class":1096},"upc",[1086,81844,1159],{"class":1146},[1086,81846,1227],{"class":1146},[1086,81848,19571],{"class":1146},[1086,81850,1455],{"class":1146},[1086,81852,81853],{"class":1088,"line":19987},[1086,81854,3390],{"emptyLinePlaceholder":738},[1086,81856,81857,81860,81862,81864,81866,81868,81870,81872,81874,81876,81878,81880],{"class":1088,"line":20007},[1086,81858,81859],{"class":1436},"    release_detail ",[1086,81861,1440],{"class":1146},[1086,81863,79990],{"class":1436},[1086,81865,861],{"class":1146},[1086,81867,80075],{"class":1105},[1086,81869,1398],{"class":1146},[1086,81871,81793],{"class":1105},[1086,81873,1227],{"class":1146},[1086,81875,1195],{"class":1146},[1086,81877,18081],{"class":1096},[1086,81879,1159],{"class":1146},[1086,81881,1455],{"class":1146},[1086,81883,81884,81886,81888,81890,81892,81895,81897,81899,81901,81903,81905,81907,81909,81911,81913],{"class":1088,"line":20013},[1086,81885,80901],{"class":1436},[1086,81887,861],{"class":1146},[1086,81889,80075],{"class":1105},[1086,81891,1398],{"class":1146},[1086,81893,81894],{"class":1105},"release_detail",[1086,81896,1227],{"class":1146},[1086,81898,1195],{"class":1146},[1086,81900,16322],{"class":1096},[1086,81902,1159],{"class":1146},[1086,81904,6786],{"class":1146},[1086,81906,1018],{"class":4109},[1086,81908,19552],{"class":1146},[1086,81910,1195],{"class":1146},[1086,81912,10398],{"class":1096},[1086,81914,4441],{"class":1146},[1086,81916,81917],{"class":1088,"line":20021},[1086,81918,3390],{"emptyLinePlaceholder":738},[1086,81920,81921],{"class":1088,"line":20051},[1086,81922,81923],{"class":1427},"    # Deal list\n",[1086,81925,81926,81929,81931,81933,81935,81937,81939,81941,81943,81945,81947,81949],{"class":1088,"line":20072},[1086,81927,81928],{"class":1436},"    deal_list ",[1086,81930,1440],{"class":1146},[1086,81932,79990],{"class":1436},[1086,81934,861],{"class":1146},[1086,81936,80075],{"class":1105},[1086,81938,1398],{"class":1146},[1086,81940,80080],{"class":1105},[1086,81942,1227],{"class":1146},[1086,81944,1195],{"class":1146},[1086,81946,18179],{"class":1096},[1086,81948,1159],{"class":1146},[1086,81950,1455],{"class":1146},[1086,81952,81953,81956,81958,81960,81962,81964,81966,81969,81971,81973,81975,81977],{"class":1088,"line":20077},[1086,81954,81955],{"class":1436},"    deal ",[1086,81957,1440],{"class":1146},[1086,81959,79990],{"class":1436},[1086,81961,861],{"class":1146},[1086,81963,80075],{"class":1105},[1086,81965,1398],{"class":1146},[1086,81967,81968],{"class":1105},"deal_list",[1086,81970,1227],{"class":1146},[1086,81972,1195],{"class":1146},[1086,81974,18197],{"class":1096},[1086,81976,1159],{"class":1146},[1086,81978,1455],{"class":1146},[1086,81980,81981,81984,81986,81988,81990,81992,81994,81997,81999,82001,82003,82005],{"class":1088,"line":20083},[1086,81982,81983],{"class":1436},"    deal_terms ",[1086,81985,1440],{"class":1146},[1086,81987,79990],{"class":1436},[1086,81989,861],{"class":1146},[1086,81991,80075],{"class":1105},[1086,81993,1398],{"class":1146},[1086,81995,81996],{"class":1105},"deal",[1086,81998,1227],{"class":1146},[1086,82000,1195],{"class":1146},[1086,82002,11361],{"class":1096},[1086,82004,1159],{"class":1146},[1086,82006,1455],{"class":1146},[1086,82008,82009,82011,82013,82015,82017,82020,82022,82024,82026,82028,82030,82032,82034,82036,82038],{"class":1088,"line":20095},[1086,82010,80901],{"class":1436},[1086,82012,861],{"class":1146},[1086,82014,80075],{"class":1105},[1086,82016,1398],{"class":1146},[1086,82018,82019],{"class":1105},"deal_terms",[1086,82021,1227],{"class":1146},[1086,82023,1195],{"class":1146},[1086,82025,18273],{"class":1096},[1086,82027,1159],{"class":1146},[1086,82029,6786],{"class":1146},[1086,82031,1018],{"class":4109},[1086,82033,19552],{"class":1146},[1086,82035,1195],{"class":1146},[1086,82037,11375],{"class":1096},[1086,82039,4441],{"class":1146},[1086,82041,82042,82044,82046,82048,82050,82052,82054,82056,82058,82060,82062,82064,82066,82068,82070],{"class":1088,"line":20112},[1086,82043,80901],{"class":1436},[1086,82045,861],{"class":1146},[1086,82047,80075],{"class":1105},[1086,82049,1398],{"class":1146},[1086,82051,82019],{"class":1105},[1086,82053,1227],{"class":1146},[1086,82055,1195],{"class":1146},[1086,82057,18290],{"class":1096},[1086,82059,1159],{"class":1146},[1086,82061,6786],{"class":1146},[1086,82063,1018],{"class":4109},[1086,82065,19552],{"class":1146},[1086,82067,1195],{"class":1146},[1086,82069,11393],{"class":1096},[1086,82071,4441],{"class":1146},[1086,82073,82074],{"class":1088,"line":20117},[1086,82075,3390],{"emptyLinePlaceholder":738},[1086,82077,82078,82081,82083,82085,82087,82089,82091,82093,82095,82097,82099,82101],{"class":1088,"line":20139},[1086,82079,82080],{"class":1436},"    validity ",[1086,82082,1440],{"class":1146},[1086,82084,79990],{"class":1436},[1086,82086,861],{"class":1146},[1086,82088,80075],{"class":1105},[1086,82090,1398],{"class":1146},[1086,82092,82019],{"class":1105},[1086,82094,1227],{"class":1146},[1086,82096,1195],{"class":1146},[1086,82098,18325],{"class":1096},[1086,82100,1159],{"class":1146},[1086,82102,1455],{"class":1146},[1086,82104,82105,82107,82109,82111,82113,82116,82118,82120,82122,82124,82126,82128,82130,82132,82134,82136,82138,82140,82143,82145,82147,82149],{"class":1088,"line":20169},[1086,82106,80901],{"class":1436},[1086,82108,861],{"class":1146},[1086,82110,80075],{"class":1105},[1086,82112,1398],{"class":1146},[1086,82114,82115],{"class":1105},"validity",[1086,82117,1227],{"class":1146},[1086,82119,1195],{"class":1146},[1086,82121,11406],{"class":1096},[1086,82123,1159],{"class":1146},[1086,82125,6786],{"class":1146},[1086,82127,1018],{"class":4109},[1086,82129,19552],{"class":1146},[1086,82131,81385],{"class":1436},[1086,82133,861],{"class":1146},[1086,82135,10812],{"class":1105},[1086,82137,1398],{"class":1146},[1086,82139,1159],{"class":1146},[1086,82141,82142],{"class":1096},"release_date",[1086,82144,1159],{"class":1146},[1086,82146,1227],{"class":1146},[1086,82148,19571],{"class":1146},[1086,82150,1455],{"class":1146},[1086,82152,82153],{"class":1088,"line":20197},[1086,82154,3390],{"emptyLinePlaceholder":738},[1086,82156,82157,82159],{"class":1088,"line":20227},[1086,82158,1460],{"class":1423},[1086,82160,82161],{"class":1436}," root\n",[1086,82163,82164],{"class":1088,"line":20232},[1086,82165,3390],{"emptyLinePlaceholder":738},[1086,82167,82168],{"class":1088,"line":23544},[1086,82169,3390],{"emptyLinePlaceholder":738},[1086,82171,82172],{"class":1088,"line":23560},[1086,82173,82174],{"class":1427},"# Usage\n",[1086,82176,82177,82180,82182],{"class":1088,"line":23565},[1086,82178,82179],{"class":1436},"track ",[1086,82181,1440],{"class":1146},[1086,82183,1164],{"class":1146},[1086,82185,82186,82188,82190,82192,82194,82196,82199,82201],{"class":1088,"line":23570},[1086,82187,1169],{"class":1146},[1086,82189,9069],{"class":1096},[1086,82191,1159],{"class":1146},[1086,82193,1133],{"class":1146},[1086,82195,1195],{"class":1146},[1086,82197,82198],{"class":1096},"Ocean Waves",[1086,82200,1159],{"class":1146},[1086,82202,1202],{"class":1146},[1086,82204,82205,82207,82209,82211,82213,82215,82218,82220],{"class":1088,"line":23593},[1086,82206,1169],{"class":1146},[1086,82208,1402],{"class":1096},[1086,82210,1159],{"class":1146},[1086,82212,1133],{"class":1146},[1086,82214,1195],{"class":1146},[1086,82216,82217],{"class":1096},"USRC12345678",[1086,82219,1159],{"class":1146},[1086,82221,1202],{"class":1146},[1086,82223,82224,82226,82228,82230,82232,82234,82237,82239],{"class":1088,"line":23613},[1086,82225,1169],{"class":1146},[1086,82227,30984],{"class":1096},[1086,82229,1159],{"class":1146},[1086,82231,1133],{"class":1146},[1086,82233,1195],{"class":1146},[1086,82235,82236],{"class":1096},"PT4M12S",[1086,82238,1159],{"class":1146},[1086,82240,1202],{"class":1146},[1086,82242,82243,82245,82247,82249,82251,82253,82255,82257],{"class":1088,"line":23644},[1086,82244,1169],{"class":1146},[1086,82246,81842],{"class":1096},[1086,82248,1159],{"class":1146},[1086,82250,1133],{"class":1146},[1086,82252,1195],{"class":1146},[1086,82254,11198],{"class":1096},[1086,82256,1159],{"class":1146},[1086,82258,1202],{"class":1146},[1086,82260,82261,82263,82265,82267,82269,82271,82274,82276],{"class":1088,"line":23649},[1086,82262,1169],{"class":1146},[1086,82264,82142],{"class":1096},[1086,82266,1159],{"class":1146},[1086,82268,1133],{"class":1146},[1086,82270,1195],{"class":1146},[1086,82272,82273],{"class":1096},"2024-01-15",[1086,82275,1159],{"class":1146},[1086,82277,1202],{"class":1146},[1086,82279,82280,82282,82284,82286,82288],{"class":1088,"line":23655},[1086,82281,1169],{"class":1146},[1086,82283,17329],{"class":1096},[1086,82285,1159],{"class":1146},[1086,82287,1133],{"class":1146},[1086,82289,6580],{"class":1146},[1086,82291,82292,82294,82296,82298,82300,82302,82304,82307,82309,82311,82313,82315,82317,82319,82321,82323,82325],{"class":1088,"line":23670},[1086,82293,17340],{"class":1146},[1086,82295,1159],{"class":1146},[1086,82297,4184],{"class":1096},[1086,82299,1159],{"class":1146},[1086,82301,1133],{"class":1146},[1086,82303,1195],{"class":1146},[1086,82305,82306],{"class":1096},"Jane Doe",[1086,82308,1159],{"class":1146},[1086,82310,1227],{"class":1146},[1086,82312,1195],{"class":1146},[1086,82314,17362],{"class":1096},[1086,82316,1159],{"class":1146},[1086,82318,1133],{"class":1146},[1086,82320,1195],{"class":1146},[1086,82322,11328],{"class":1096},[1086,82324,1159],{"class":1146},[1086,82326,17375],{"class":1146},[1086,82328,82329,82331,82333,82335,82337,82339,82341,82344,82346,82348,82350,82352,82354,82356,82358,82361,82363],{"class":1088,"line":23679},[1086,82330,17340],{"class":1146},[1086,82332,1159],{"class":1146},[1086,82334,4184],{"class":1096},[1086,82336,1159],{"class":1146},[1086,82338,1133],{"class":1146},[1086,82340,1195],{"class":1146},[1086,82342,82343],{"class":1096},"John Smith",[1086,82345,1159],{"class":1146},[1086,82347,1227],{"class":1146},[1086,82349,1195],{"class":1146},[1086,82351,17362],{"class":1096},[1086,82353,1159],{"class":1146},[1086,82355,1133],{"class":1146},[1086,82357,1195],{"class":1146},[1086,82359,82360],{"class":1096},"FeaturedArtist",[1086,82362,1159],{"class":1146},[1086,82364,17375],{"class":1146},[1086,82366,82367],{"class":1088,"line":23691},[1086,82368,17417],{"class":1146},[1086,82370,82371],{"class":1088,"line":23708},[1086,82372,1291],{"class":1146},[1086,82374,82375],{"class":1088,"line":23724},[1086,82376,3390],{"emptyLinePlaceholder":738},[1086,82378,82379,82382,82384,82386],{"class":1088,"line":23737},[1086,82380,82381],{"class":1436},"ern ",[1086,82383,1440],{"class":1146},[1086,82385,80746],{"class":1105},[1086,82387,4094],{"class":1146},[1086,82389,82390,82393,82395,82397],{"class":1088,"line":23742},[1086,82391,82392],{"class":1401},"    track_data",[1086,82394,1440],{"class":1146},[1086,82396,33099],{"class":1105},[1086,82398,1202],{"class":1146},[1086,82400,82401,82404,82406,82408,82410,82412,82414,82416,82418,82420,82422,82424,82426,82428,82430,82432,82434,82436],{"class":1088,"line":23747},[1086,82402,82403],{"class":1401},"    sender",[1086,82405,1553],{"class":1146},[1086,82407,1159],{"class":1146},[1086,82409,81078],{"class":1096},[1086,82411,1159],{"class":1146},[1086,82413,1133],{"class":1146},[1086,82415,1195],{"class":1146},[1086,82417,80225],{"class":1096},[1086,82419,1159],{"class":1146},[1086,82421,1227],{"class":1146},[1086,82423,1195],{"class":1146},[1086,82425,4184],{"class":1096},[1086,82427,1159],{"class":1146},[1086,82429,1133],{"class":1146},[1086,82431,1195],{"class":1146},[1086,82433,80258],{"class":1096},[1086,82435,1159],{"class":1146},[1086,82437,17375],{"class":1146},[1086,82439,82440,82443,82445,82447,82449,82451,82453,82455,82457,82459,82461,82463,82465,82467,82469,82471,82473,82475],{"class":1088,"line":23763},[1086,82441,82442],{"class":1401},"    recipient",[1086,82444,1553],{"class":1146},[1086,82446,1159],{"class":1146},[1086,82448,81078],{"class":1096},[1086,82450,1159],{"class":1146},[1086,82452,1133],{"class":1146},[1086,82454,1195],{"class":1146},[1086,82456,80324],{"class":1096},[1086,82458,1159],{"class":1146},[1086,82460,1227],{"class":1146},[1086,82462,1195],{"class":1146},[1086,82464,4184],{"class":1096},[1086,82466,1159],{"class":1146},[1086,82468,1133],{"class":1146},[1086,82470,1195],{"class":1146},[1086,82472,58345],{"class":1096},[1086,82474,1159],{"class":1146},[1086,82476,17375],{"class":1146},[1086,82478,82479],{"class":1088,"line":23792},[1086,82480,1455],{"class":1146},[1086,82482,82483],{"class":1088,"line":23814},[1086,82484,3390],{"emptyLinePlaceholder":738},[1086,82486,82487,82489,82491,82493,82495,82497,82499,82502],{"class":1088,"line":23835},[1086,82488,80593],{"class":1436},[1086,82490,1440],{"class":1146},[1086,82492,79990],{"class":1436},[1086,82494,861],{"class":1146},[1086,82496,79966],{"class":1105},[1086,82498,1398],{"class":1146},[1086,82500,82501],{"class":1105},"ern",[1086,82503,1455],{"class":1146},[1086,82505,82506,82508,82510,82512,82514,82516,82518,82520,82522,82524,82526],{"class":1088,"line":23866},[1086,82507,80096],{"class":1436},[1086,82509,861],{"class":1146},[1086,82511,80616],{"class":1105},[1086,82513,1398],{"class":1146},[1086,82515,80621],{"class":1105},[1086,82517,1227],{"class":1146},[1086,82519,80626],{"class":1401},[1086,82521,1440],{"class":1146},[1086,82523,1159],{"class":1146},[1086,82525,1152],{"class":1146},[1086,82527,1455],{"class":1146},[1086,82529,82530,82532,82534,82536,82538,82540,82543,82545,82547,82549,82551,82553,82555,82557,82559,82561],{"class":1088,"line":23872},[1086,82531,80621],{"class":1436},[1086,82533,861],{"class":1146},[1086,82535,29046],{"class":1105},[1086,82537,1398],{"class":1146},[1086,82539,1159],{"class":1146},[1086,82541,82542],{"class":1096},"release_complete.xml",[1086,82544,1159],{"class":1146},[1086,82546,1227],{"class":1146},[1086,82548,80656],{"class":1401},[1086,82550,22246],{"class":1146},[1086,82552,29021],{"class":1401},[1086,82554,1440],{"class":1146},[1086,82556,1159],{"class":1146},[1086,82558,80667],{"class":1096},[1086,82560,1159],{"class":1146},[1086,82562,1455],{"class":1146},[1086,82564,82565,82567,82569,82571,82574,82576],{"class":1088,"line":61687},[1086,82566,10725],{"class":1105},[1086,82568,1398],{"class":1146},[1086,82570,1159],{"class":1146},[1086,82572,82573],{"class":1096},"Complete DDEX ERN file generated.",[1086,82575,1159],{"class":1146},[1086,82577,1455],{"class":1146},[1901,82579,82580],{},[842,82581,82582],{},"Handle tracks with multiple artists carefully — ensure each artist element is properly structured with the correct role. This is a common source of validation errors.",[863,82584,82586],{"id":82585},"what-to-pay-attention-to","What to Pay Attention To",[958,82588,82589,82595,82601,82607],{},[961,82590,82591,82594],{},[996,82592,82593],{},"DDEX for takedowns"," uses a different message structure with serious legal consequences — handle separately",[961,82596,82597,82600],{},[996,82598,82599],{},"Version compatibility"," varies by platform (YouTube: 3.4–3.8, Facebook: 3.8.3, newest: 4.2)",[961,82602,82603,82606],{},[996,82604,82605],{},"Validation is essential"," — always validate generated XML against the official XSD schemas before submission",[961,82608,82609],{},"This guide covers single music files only. Videos and other media types require different approaches and more detailed metadata",[863,82611,82613],{"id":82612},"why-use-ddex","Why Use DDEX?",[842,82615,82616],{},"DDEX standards are widely adopted because they solve real problems: interoperability between systems, automated workflows that reduce manual errors, and consistent data formats that ensure artists and rights holders get paid accurately. Most major digital music platforms require DDEX compliance for content delivery.",[1572,82618,82619],{},[842,82620,82621,82622,82627,82628,82633,82634,861],{},"For more on DDEX, see the ",[846,82623,82626],{"href":82624,"rel":82625},"https://kb.ddex.net/",[850],"DDEX Knowledge Base",", the ",[846,82629,82632],{"href":82630,"rel":82631},"https://kb.ddex.net/about-ddex-standards/ddex-development-and-maintenance/",[850],"backward compatibility guide",", and the ",[846,82635,82638],{"href":82636,"rel":82637},"https://kb.ddex.net/reference-material/past-versions-of-standards/",[850],"list of breaking updates",[1680,82640,82641],{},"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 .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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":728,"searchDepth":729,"depth":729,"links":82643},[82644,82645,82649,82650],{"id":79871,"depth":729,"text":79872},{"id":79900,"depth":729,"text":79901,"children":82646},[82647,82648],{"id":79936,"depth":1112,"text":79937},{"id":80693,"depth":1112,"text":80694},{"id":82585,"depth":729,"text":82586},{"id":82612,"depth":729,"text":82613},"2023-12-07T00:00:00.000Z","Learn what DDEX files are and how to generate them using Python. Covers ERN, DSR, and RIN standards for digital music data exchange in the industry.",{"src":82654},"/images/blog/musictechlab_blog_introduction-to-generating-ddex-file-using-python.webp",{"enabled":738,"items":82656},[82657,82659,82661,82663],{"text":82658,"icon":5365},"DDEX ERN files can be generated with Python's built-in xml.etree.ElementTree module.",{"text":82660,"icon":3850},"A free DPID is required to identify the sender; no DDEX membership needed to obtain one.",{"text":82662,"icon":3847},"Version compatibility varies by platform: YouTube supports 3.4-3.8, Facebook requires 3.8.3.",{"text":82664,"icon":7495},"Always validate generated XML against official XSD schemas before submitting to any DSP.",{},{"title":156,"description":82652},[9763,18784,3857],"IfkYZXP75b51909IVUbhsodw2qm0IdstdMvTAks2cYg",{"id":82670,"title":184,"authors":82671,"badge":723,"body":82674,"category":731,"client":723,"date":82838,"description":82839,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":82840,"keyTakeaways":82842,"meta":82852,"navigation":738,"path":185,"seo":82853,"status":723,"stem":186,"tags":82854,"teaser":723,"__hash__":82855},"posts/blog/music-data/self-publishing-in-the-music-industry-a-tale-of-emuze-me.md",[82672],{"name":74344,"avatar":82673},{"src":74346},{"type":725,"value":82675,"toc":82831},[82676,82679,82682,82684,82688,82691,82693,82697,82703,82706,82709,82728,82733,82735,82739,82742,82758,82763,82765,82769,82772,82793,82798,82800,82804,82807,82823,82828],[842,82677,82678],{},"Self-publishing in music has shifted power from traditional record labels to individual artists. For decades, a few major labels controlled distribution and determined which artists succeeded. That model left creators dependent on labels for both exposure and income.",[842,82680,82681],{},"The advent of the internet and digital technologies in the late 20th century began to challenge this status quo. In the early 2000s, the proliferation of digital recording software and platforms like MySpace gave rise to a new era where artists could record and share their music online with minimal cost.",[4937,82683],{},[863,82685,82687],{"id":82686},"the-evolution-of-music-distribution","The Evolution of Music Distribution",[3572,82689],{":items":82690},"[{\"title\":\"Early 2000s — MySpace Era\",\"description\":\"Digital recording software and platforms like MySpace let artists record and share music online with minimal cost. The first major step towards democratizing music production.\",\"icon\":\"i-lucide-music\"},{\"title\":\"Mid-2000s — iTunes Revolution\",\"description\":\"Artists could distribute music directly to a global audience and retain a greater portion of their earnings. The traditional distribution model was disrupted.\",\"icon\":\"i-lucide-store\"},{\"title\":\"2010s — Streaming Takes Over\",\"description\":\"Spotify, SoundCloud, and YouTube offered even more avenues for independent publishing, reaching listeners worldwide and transcending geographical barriers.\",\"icon\":\"i-lucide-radio\"},{\"title\":\"Today — Self-Publishing Is Mainstream\",\"description\":\"Artists control their creative output and reach audiences worldwide. Self-publishing is no longer an edgy alternative but a core part of the music industry.\",\"icon\":\"i-lucide-globe\"}]",[4937,82692],{},[863,82694,82696],{"id":82695},"digital-distribution-benefits-and-challenges","Digital Distribution: Benefits and Challenges",[842,82698,82699],{},[1027,82700],{"alt":82701,"src":82702},"Digital Distribution","/images/cdn-migrated/techivation-unsplash.webp",[842,82704,82705],{},"Technological advancements have played a crucial role in these changes. The advent of digital recording has democratized music production, making it more accessible and allowing for greater creative freedom. The shift from physical mediums like cassettes and CDs to digital formats has been drastic — physical formats have now become fetishized relics, replaced by digital recording and streaming services.",[842,82707,82708],{},"The digital era has also redefined artist-fan interactions. Social media and streaming platforms have bridged the gap between artists and their audiences, facilitating direct and more personal connections.",[1045,82710,82712,82716,82720,82724],{"className":82711},[1048,1049,1765,1051,1052],[1054,82713],{"description":82714,"icon":3920,"title":82715},"Artists can reach global audiences instantly without label backing or physical distribution networks.","Wider Exposure",[1054,82717],{"description":82718,"icon":4845,"title":82719},"Social media and streaming platforms enable personal, direct engagement with audiences.","Direct Fan Connections",[1054,82721],{"description":82722,"icon":3847,"title":82723},"The ease of digital distribution has made it increasingly difficult for artists to stand out.","Market Oversaturation",[1054,82725],{"description":82726,"icon":1774,"title":82727},"Streaming offers wider exposure but often provides lower per-stream payouts compared to traditional sales.","Lower Per-Stream Payouts",[1901,82729,82730],{},[842,82731,82732],{},"Artists have had to diversify their income sources, turning to live performances, online concerts, and placements in TV, films, and commercials to compensate for lower streaming revenue.",[4937,82734],{},[863,82736,82738],{"id":82737},"monetization-and-accessibility","Monetization and Accessibility",[842,82740,82741],{},"Digital distribution platforms like DistroKid, CD Baby, and TuneCore have become the primary avenue for music streaming and downloading. They act as intermediaries between artists and streaming services like Spotify, Apple Music, and Tidal.",[1045,82743,82745,82749,82754],{"className":82744},[1048,1049,1050,1051,1052],[1054,82746],{"description":82747,"icon":13638,"title":82748},"Artists provide sound files and metadata. The platform replicates data across all services, ensuring consistency.","Streamlined Upload",[1054,82750],{"description":82751,"icon":82752,"title":82753},"Platforms track streams and downloads across services, collecting and paying out royalties automatically.","i-lucide-wallet","Royalty Management",[1054,82755],{"description":82756,"icon":1067,"title":82757},"Music becomes available on dozens of streaming services simultaneously with a single upload.","Global Reach",[1572,82759,82760],{},[842,82761,82762],{},"The sheer volume of music on streaming platforms is both an advantage and a challenge. Standing out in an overcrowded digital space requires strategy, not just talent. Playlists play a crucial role, but securing a spot often requires industry connections or curator favour.",[4937,82764],{},[863,82766,82768],{"id":82767},"emuzeme-self-publishing-done-right","Emuze.me: Self-Publishing Done Right",[842,82770,82771],{},"Amongst these challenges, a local Polish service emerges as a quintessential example of a platform embracing the global trends of self-publishing and digital distribution. Created by e-muzyka, a part of the renowned Empik Group, emuze.me stands on a foundation of deep industry experience and technological expertise.",[1045,82773,82775,82779,82784,82789],{"className":82774},[1048,1049,1765,1051,1052],[1054,82776],{"description":82777,"icon":37696,"title":82778},"Upload and distribute music to over 50 digital services worldwide, bypassing traditional barriers like distributor negotiations.","Direct Distribution",[1054,82780],{"description":82781,"icon":82782,"title":82783},"Artists retain up to 85% of their sales revenue without restrictive long-term contracts — rare flexibility in the Polish market.","i-lucide-percent","85% Revenue Share",[1054,82785],{"description":82786,"icon":82787,"title":82788},"Promotion across digital services, social media campaigns, and inclusion in newsletters to Polish media outlets.","i-lucide-megaphone","Marketing Support",[1054,82790],{"description":82791,"icon":7546,"title":82792},"Designed for clarity and simplicity, accommodating artists at various stages of their career.","Simple Interface",[1032,82794,82795],{},[842,82796,82797],{},"Emuze.me's impact on the music industry is significant. By integrating self-publishing and digital distribution, the platform expands artists' global reach while helping them creatively and financially — a testament to building fair digital platforms in music.",[4937,82799],{},[863,82801,82803],{"id":82802},"future-perspectives","Future Perspectives",[842,82805,82806],{},"As we look toward the future of music distribution, advances in technology are expected to play significant roles.",[1045,82808,82810,82814,82818],{"className":82809},[1048,1049,1050,1051,1052],[1054,82811],{"description":82812,"icon":11614,"title":82813},"Artificial intelligence could personalize music discovery further, offering sophisticated data analytics and marketing strategies.","AI-Powered Discovery",[1054,82815],{"description":82816,"icon":8737,"title":82817},"More transparent and efficient royalty distribution systems, giving artists greater control over intellectual property.","Blockchain Royalties",[1054,82819],{"description":82820,"icon":82821,"title":82822},"Virtual and augmented reality may offer new ways for artists to present music and engage with fans.","i-lucide-glasses","Immersive Experiences",[1901,82824,82825],{},[842,82826,82827],{},"A significant risk is the proliferation of AI-generated music, which can flood the market with vast amounts of content, making it harder for human artists to stand out and potentially exerting downward pressure on the value of music.",[842,82829,82830],{},"In this rapidly evolving domain, adaptability and innovation will be key. Platforms that can leverage new technologies while providing real support to artists and their creative freedom are likely to lead the way in the future of music distribution.",{"title":728,"searchDepth":729,"depth":729,"links":82832},[82833,82834,82835,82836,82837],{"id":82686,"depth":729,"text":82687},{"id":82695,"depth":729,"text":82696},{"id":82737,"depth":729,"text":82738},{"id":82767,"depth":729,"text":82768},{"id":82802,"depth":729,"text":82803},"2023-11-13T00:00:00.000Z","How self-publishing evolved in the music industry, shifting control from record labels to independent artists, and how Emuze.me empowers creators today.",{"src":82841},"/images/blog/musictechlab_blog_self-publishing-in-the-music-industry-a-tale-of-emuze-me.webp",{"enabled":738,"items":82843},[82844,82846,82848,82850],{"text":82845,"icon":9547},"Self-publishing shifted power from major labels to independent artists over two decades.",{"text":82847,"icon":5504},"Emuze.me offers 85% revenue share and distribution to 50+ services without long-term contracts.",{"text":82849,"icon":3920},"Streaming provides wider exposure but lower per-stream payouts than traditional sales.",{"text":82851,"icon":11614},"AI-generated music risks flooding platforms and making it harder for human artists to stand out.",{},{"title":184,"description":82839},[5523,731,3192],"i-mVky4av__wlZUtY49WTl9RnNMDNC0LQ5Dakdw51cI",{"id":82857,"title":346,"authors":82858,"badge":723,"body":82861,"category":756,"client":723,"date":83039,"description":83040,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":83041,"keyTakeaways":83043,"meta":83053,"navigation":738,"path":347,"seo":83054,"status":723,"stem":348,"tags":83055,"teaser":723,"__hash__":83056},"posts/blog/software-development/automating-success-the-art-of-unified-documentation.md",[82859],{"name":70989,"avatar":82860},{"src":70991},{"type":725,"value":82862,"toc":83029},[82863,82865,82868,82870,82874,82876,82879,82881,82901,82903,82907,82909,82912,82914,82918,82920,82923,82925,82957,82959,82963,82965,82968,82970,82974,82976,82979,82981,82985,82987,82990,82992,82996,82998,83001,83003,83020,83022,83024,83026],[842,82864,40867],{},[842,82866,82867],{},"In the rapidly evolving world of software development, maintaining extensive and current documentation can often seem like an uphill battle. With each component - from APIs to databases, user interfaces, and now, the sprawling complexities of Kubernetes - carrying its unique documentation requirements, the process becomes even more daunting. However, the advent of automated documentation is changing the narrative, turning what was once a grueling task into a streamlined, integrated component of the development lifecycle.",[842,82869,52316],{},[863,82871,82873],{"id":82872},"the-diverse-documentation-challenge","The Diverse Documentation Challenge",[842,82875,52316],{},[842,82877,82878],{},"The first hurdle in any project is establishing a consistent and efficient way to handle the documentation for various components:",[842,82880,52316],{},[958,82882,82883,82886,82889,82892,82895,82898],{},[961,82884,82885],{},"APIs: Require detailed explanations of endpoints, parameters, and expected responses.",[961,82887,82888],{},"Protobuf protos: Must have clearly defined structures and usage guidelines.",[961,82890,82891],{},"Database Schemas: Demand accurate schema representations and relationship diagrams.",[961,82893,82894],{},"User Interfaces (UIs): Need interactive documentation that details component hierarchies and design systems.",[961,82896,82897],{},"Infrastructure as Code (IaC): Ought to be accompanied by comprehensive guides on infrastructure setup and maintenance.",[961,82899,82900],{},"Kubernetes configurations: Involve complex configurations that benefit greatly from automated visualization and documentation generation.",[842,82902,52316],{},[863,82904,82906],{"id":82905},"automated-documentation-the-unified-strategy","Automated Documentation: The Unified Strategy",[842,82908,52316],{},[842,82910,82911],{},"To create a unified documentation repository that is both comprehensive and maintainable, a multi-pronged strategy is required:",[842,82913,52316],{},[863,82915,82917],{"id":82916},"automated-generation","Automated Generation",[842,82919,52316],{},[842,82921,82922],{},"Each component should have documentation generated automatically. For instance:",[842,82924,52316],{},[958,82926,82927,82930,82937,82940,82943,82950],{},[961,82928,82929],{},"APIs can be documented using tools like Swagger or Redoc that interpret OpenAPI specifications.",[961,82931,82932,82933,82936],{},"Protobuf comments can be parsed by ",[895,82934,82935],{},"protoc-gen-doc"," to generate cohesive guides.",[961,82938,82939],{},"Database schemas benefit from tools like SchemaSpy for ER diagrams and documentation.",[961,82941,82942],{},"User interfaces are well-served by Storybook to showcase and document components.",[961,82944,82945,82946,82949],{},"Infrastructure as Code setups can utilize ",[895,82947,82948],{},"terraform-docs"," for documentation generation.",[961,82951,82952,82953,82956],{},"Kubernetes configurations demand tools like ",[895,82954,82955],{},"kube-docs",", which generate documentation from YAML manifests, providing essential insights into cluster setups and deployment strategies.",[842,82958,52316],{},[863,82960,82962],{"id":82961},"standardization-and-integration","Standardization and Integration",[842,82964,52316],{},[842,82966,82967],{},"Adopting standard documentation formats across different tools facilitates easier integration. Markdown and HTML are widely supported and can be displayed and converted with ease. Once documentation is generated, it must be aggregated into a unified format via an automated pipeline, ideally within the CI/CD process.",[842,82969,52316],{},[863,82971,82973],{"id":82972},"version-control-and-single-source-of-truth","Version Control and Single Source of Truth",[842,82975,52316],{},[842,82977,82978],{},"All documentation should be version-controlled, ensuring that updates are synchronized with the actual state of the project. This becomes the project’s single source of truth - a living document that evolves with the codebase.",[842,82980,52316],{},[863,82982,82984],{"id":82983},"presentation-and-accessibility","Presentation and Accessibility",[842,82986,52316],{},[842,82988,82989],{},"The final piece is the presentation layer. Using tools that can present the amalgamated documentation in an accessible and interactive format is crucial. Redoc or GitBook can serve as interfaces for APIs, while Kubernetes dashboard tools can visualize cluster information.",[842,82991,52316],{},[863,82993,82995],{"id":82994},"the-advantages-of-automation","The Advantages of Automation",[842,82997,52316],{},[842,82999,83000],{},"Automated documentation systems bring numerous benefits to the table:",[842,83002,52316],{},[958,83004,83005,83008,83011,83014,83017],{},[961,83006,83007],{},"Currency and Accuracy: Documentation that is automatically generated from source code is always up-to-date with the latest changes.",[961,83009,83010],{},"Efficiency: Developers spend less time writing and updating documentation, as most of the process is automated.",[961,83012,83013],{},"Consistency: A standardized approach means that documentation across different components has a uniform look and feel.",[961,83015,83016],{},"Discovery: With all documentation in one place, it's easier for team members to find and use the information they need.",[961,83018,83019],{},"Scalability: As new components are added, the documentation system can scale to accommodate them without additional overhead.",[842,83021,52316],{},[863,83023,18681],{"id":18680},[842,83025,52316],{},[842,83027,83028],{},"In conclusion, automated documentation isn't just a convenience; it's a necessity in a landscape of ever-increasing complexity. By adopting a unified documentation strategy that brings together automated tools, standardization, and integration, teams can create a dynamic, living documentation ecosystem. This system supports the project across its lifecycle, improves cross-team collaboration, and ultimately leads to a more robust and sustainable product. With every component from API to Kubernetes captured within a single documentation narrative, software projects can achieve clarity, coherence, and continuity, propelling them towards success.",{"title":728,"searchDepth":729,"depth":729,"links":83030},[83031,83032,83033,83034,83035,83036,83037,83038],{"id":82872,"depth":729,"text":82873},{"id":82905,"depth":729,"text":82906},{"id":82916,"depth":729,"text":82917},{"id":82961,"depth":729,"text":82962},{"id":82972,"depth":729,"text":82973},{"id":82983,"depth":729,"text":82984},{"id":82994,"depth":729,"text":82995},{"id":18680,"depth":729,"text":18681},"2023-11-07T00:00:00.000Z","Why unified automated documentation is essential for modern software teams. Learn strategies to keep docs dynamic and up to date across your stack.",{"src":83042},"/images/blog/musictechlab_blog_automating-success-the-art-of-unified-documentation.webp",{"enabled":738,"items":83044},[83045,83047,83049,83051],{"text":83046,"icon":5365},"Auto-generate docs from code: Swagger for APIs, SchemaSpy for databases, Storybook for UI.",{"text":83048,"icon":37696},"Integrate doc generation into CI/CD so documentation stays current with every deploy.",{"text":83050,"icon":1769},"Standardize on Markdown or HTML across all tools for easier aggregation.",{"text":83052,"icon":8737},"Kubernetes configs benefit from automated visualization using tools like kube-docs.",{},{"title":346,"description":83040},[15279,18784],"30s6c1_sqaxvDkC-3z5Q8b3C1eygscbbuLBbs-R1lEM",{"id":83058,"title":152,"authors":83059,"badge":723,"body":83064,"category":731,"client":723,"date":84146,"description":84147,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":84148,"keyTakeaways":84150,"meta":84158,"navigation":738,"path":153,"seo":84159,"status":723,"stem":154,"tags":84160,"teaser":723,"__hash__":84162},"posts/blog/music-data/hybrid-database-model-in-django-as-a-performance-booster.md",[83060],{"name":83061,"avatar":83062},"Oleksandr Gorbunov",{"src":83063},"/images/people/oleksandr-gorbunov.webp",{"type":725,"value":83065,"toc":84135},[83066,83068,83071,83074,83077,83079,83082,83092,83096,83330,83337,83341,83499,83502,83506,83902,83909,83913,84088,84091,84095,84104,84115,84124,84126,84129,84132],[863,83067,27940],{"id":27939},[842,83069,83070],{},"Handling a high volume of requests per second while managing an extensive product database is a key challenge in modern e-commerce platforms. In this post, I'll show the solutions we implemented in our \"catalog\" microservice to address these challenges.",[842,83072,83073],{},"We initially built the service in Django — driven by the need for rapid development and Django's convenient admin interface. But as the platform scaled, storing everything in a relational database with multiple joins became a bottleneck.",[842,83075,83076],{},"Our idea: combine the strengths of both relational and non-relational databases. Keep the relational integrity needed for certain data, while using MongoDB for fast reads on product fields. And keep everything compatible with Django models, Django Admin, and Django REST Framework.",[863,83078,27663],{"id":52066},[842,83080,83081],{},"The goal is to link each Django model object with a MongoDB document. When reading, the model is automatically populated from MongoDB. When writing, changes are synced back to the MongoDB document.",[842,83083,83084,83085,83088,83089,861],{},"We achieve this by dynamically creating ",[996,83086,83087],{},"descriptors"," within the Django model. These descriptors act as proxies to MongoDB fields and are generated using a ",[996,83090,83091],{},"metaclass",[1074,83093,83095],{"id":83094},"step-1-the-mongodb-field-descriptor","Step 1: The MongoDB Field Descriptor",[1013,83097,83099],{"className":1368,"code":83098,"language":1250,"meta":728,"style":728},"class MongoFieldDescriptor:\n    def __init__(self, field_name):\n        self.field_name = field_name\n        self.private_name = f\"_m_{field_name}\"\n\n    def __get__(self, obj, objtype=None):\n        if obj is None:\n            return self\n        # Read directly from the MongoDB document\n        if obj._mongo_document:\n            return obj._mongo_document.get(self.field_name)\n        return getattr(obj, self.private_name, None)\n\n    def __set__(self, obj, value):\n        # Store in private field for batch saving later\n        setattr(obj, self.private_name, value)\n",[895,83100,83101,83110,83128,83142,83166,83170,83193,83205,83212,83217,83230,83254,83276,83280,83302,83307],{"__ignoreMap":728},[1086,83102,83103,83105,83108],{"class":1088,"line":1089},[1086,83104,4036],{"class":1155},[1086,83106,83107],{"class":1092}," MongoFieldDescriptor",[1086,83109,1418],{"class":1146},[1086,83111,83112,83114,83117,83119,83121,83123,83126],{"class":1088,"line":729},[1086,83113,4065],{"class":1155},[1086,83115,83116],{"class":1105}," __init__",[1086,83118,1398],{"class":1146},[1086,83120,4074],{"class":4073},[1086,83122,1227],{"class":1146},[1086,83124,83125],{"class":1401}," field_name",[1086,83127,4047],{"class":1146},[1086,83129,83130,83132,83134,83137,83139],{"class":1088,"line":1112},[1086,83131,23875],{"class":1436},[1086,83133,861],{"class":1146},[1086,83135,83136],{"class":4109},"field_name",[1086,83138,19552],{"class":1146},[1086,83140,83141],{"class":1436}," field_name\n",[1086,83143,83144,83146,83148,83151,83153,83155,83158,83160,83162,83164],{"class":1088,"line":1181},[1086,83145,23875],{"class":1436},[1086,83147,861],{"class":1146},[1086,83149,83150],{"class":4109},"private_name",[1086,83152,19552],{"class":1146},[1086,83154,4403],{"class":1155},[1086,83156,83157],{"class":1096},"\"_m_",[1086,83159,4409],{"class":1187},[1086,83161,83136],{"class":1436},[1086,83163,4423],{"class":1187},[1086,83165,4441],{"class":1096},[1086,83167,83168],{"class":1088,"line":1205},[1086,83169,3390],{"emptyLinePlaceholder":738},[1086,83171,83172,83174,83177,83179,83181,83183,83185,83187,83190],{"class":1088,"line":1276},[1086,83173,4065],{"class":1155},[1086,83175,83176],{"class":1105}," __get__",[1086,83178,1398],{"class":1146},[1086,83180,4074],{"class":4073},[1086,83182,1227],{"class":1146},[1086,83184,25404],{"class":1401},[1086,83186,1227],{"class":1146},[1086,83188,83189],{"class":1401}," objtype",[1086,83191,83192],{"class":1146},"=None):\n",[1086,83194,83195,83197,83200,83203],{"class":1088,"line":1282},[1086,83196,6800],{"class":1423},[1086,83198,83199],{"class":1436}," obj ",[1086,83201,83202],{"class":1146},"is",[1086,83204,19794],{"class":1146},[1086,83206,83207,83209],{"class":1088,"line":1288},[1086,83208,19532],{"class":1423},[1086,83210,83211],{"class":1436}," self\n",[1086,83213,83214],{"class":1088,"line":2685},[1086,83215,83216],{"class":1427},"        # Read directly from the MongoDB document\n",[1086,83218,83219,83221,83223,83225,83228],{"class":1088,"line":2700},[1086,83220,6800],{"class":1423},[1086,83222,25404],{"class":1436},[1086,83224,861],{"class":1146},[1086,83226,83227],{"class":4109},"_mongo_document",[1086,83229,1418],{"class":1146},[1086,83231,83232,83234,83236,83238,83240,83242,83244,83246,83248,83250,83252],{"class":1088,"line":3398},[1086,83233,19532],{"class":1423},[1086,83235,25404],{"class":1436},[1086,83237,861],{"class":1146},[1086,83239,83227],{"class":4109},[1086,83241,861],{"class":1146},[1086,83243,10812],{"class":1105},[1086,83245,1398],{"class":1146},[1086,83247,4074],{"class":1436},[1086,83249,861],{"class":1146},[1086,83251,83136],{"class":4109},[1086,83253,1455],{"class":1146},[1086,83255,83256,83258,83260,83262,83264,83266,83268,83270,83272,83274],{"class":1088,"line":1715},[1086,83257,4239],{"class":1423},[1086,83259,6441],{"class":1105},[1086,83261,1398],{"class":1146},[1086,83263,25552],{"class":1105},[1086,83265,1227],{"class":1146},[1086,83267,23768],{"class":1436},[1086,83269,861],{"class":1146},[1086,83271,83150],{"class":4109},[1086,83273,1227],{"class":1146},[1086,83275,60642],{"class":1146},[1086,83277,83278],{"class":1088,"line":3409},[1086,83279,3390],{"emptyLinePlaceholder":738},[1086,83281,83282,83284,83287,83289,83291,83293,83295,83297,83300],{"class":1088,"line":3415},[1086,83283,4065],{"class":1155},[1086,83285,83286],{"class":1105}," __set__",[1086,83288,1398],{"class":1146},[1086,83290,4074],{"class":4073},[1086,83292,1227],{"class":1146},[1086,83294,25404],{"class":1401},[1086,83296,1227],{"class":1146},[1086,83298,83299],{"class":1401}," value",[1086,83301,4047],{"class":1146},[1086,83303,83304],{"class":1088,"line":3421},[1086,83305,83306],{"class":1427},"        # Store in private field for batch saving later\n",[1086,83308,83309,83312,83314,83316,83318,83320,83322,83324,83326,83328],{"class":1088,"line":3427},[1086,83310,83311],{"class":1105},"        setattr",[1086,83313,1398],{"class":1146},[1086,83315,25552],{"class":1105},[1086,83317,1227],{"class":1146},[1086,83319,23768],{"class":1436},[1086,83321,861],{"class":1146},[1086,83323,83150],{"class":4109},[1086,83325,1227],{"class":1146},[1086,83327,83299],{"class":1105},[1086,83329,1455],{"class":1146},[842,83331,83332,83333,83336],{},"The descriptor reads fields directly from the MongoDB document. For writes, it stores data in a private ",[895,83334,83335],{},"_m_\u003Cfield>"," attribute so all fields can be saved at once.",[1074,83338,83340],{"id":83339},"step-2-the-model-metaclass","Step 2: The Model Metaclass",[1013,83342,83344],{"className":1368,"code":83343,"language":1250,"meta":728,"style":728},"class HybridModelMeta(type(models.Model)):\n    def __new__(mcs, name, bases, namespace):\n        cls = super().__new__(mcs, name, bases, namespace)\n        mongo_fields = getattr(cls, \"mongo_fields\", [])\n        for field_name in mongo_fields:\n            setattr(cls, field_name, MongoFieldDescriptor(field_name))\n        return cls\n",[895,83345,83346,83368,83396,83429,83455,83469,83492],{"__ignoreMap":728},[1086,83347,83348,83350,83353,83355,83357,83359,83361,83363,83365],{"class":1088,"line":1089},[1086,83349,4036],{"class":1155},[1086,83351,83352],{"class":1092}," HybridModelMeta",[1086,83354,1398],{"class":1146},[1086,83356,12011],{"class":1092},[1086,83358,1398],{"class":1146},[1086,83360,21939],{"class":1105},[1086,83362,861],{"class":1146},[1086,83364,11370],{"class":4109},[1086,83366,83367],{"class":1146},")):\n",[1086,83369,83370,83372,83375,83377,83380,83382,83384,83386,83389,83391,83394],{"class":1088,"line":729},[1086,83371,4065],{"class":1155},[1086,83373,83374],{"class":1105}," __new__",[1086,83376,1398],{"class":1146},[1086,83378,83379],{"class":1401},"mcs",[1086,83381,1227],{"class":1146},[1086,83383,24696],{"class":1401},[1086,83385,1227],{"class":1146},[1086,83387,83388],{"class":1401}," bases",[1086,83390,1227],{"class":1146},[1086,83392,83393],{"class":1401}," namespace",[1086,83395,4047],{"class":1146},[1086,83397,83398,83401,83403,83406,83408,83411,83413,83415,83417,83419,83421,83423,83425,83427],{"class":1088,"line":1112},[1086,83399,83400],{"class":1436},"        cls ",[1086,83402,1440],{"class":1146},[1086,83404,83405],{"class":1092}," super",[1086,83407,6813],{"class":1146},[1086,83409,83410],{"class":1105},"__new__",[1086,83412,1398],{"class":1146},[1086,83414,83379],{"class":1105},[1086,83416,1227],{"class":1146},[1086,83418,24696],{"class":1105},[1086,83420,1227],{"class":1146},[1086,83422,83388],{"class":1105},[1086,83424,1227],{"class":1146},[1086,83426,83393],{"class":1105},[1086,83428,1455],{"class":1146},[1086,83430,83431,83434,83436,83438,83440,83442,83444,83446,83449,83451,83453],{"class":1088,"line":1181},[1086,83432,83433],{"class":1436},"        mongo_fields ",[1086,83435,1440],{"class":1146},[1086,83437,6441],{"class":1105},[1086,83439,1398],{"class":1146},[1086,83441,19494],{"class":1436},[1086,83443,1227],{"class":1146},[1086,83445,1195],{"class":1146},[1086,83447,83448],{"class":1096},"mongo_fields",[1086,83450,1159],{"class":1146},[1086,83452,1227],{"class":1146},[1086,83454,23339],{"class":1146},[1086,83456,83457,83459,83462,83464,83467],{"class":1088,"line":1205},[1086,83458,6903],{"class":1423},[1086,83460,83461],{"class":1436}," field_name ",[1086,83463,5931],{"class":1423},[1086,83465,83466],{"class":1436}," mongo_fields",[1086,83468,1418],{"class":1146},[1086,83470,83471,83474,83476,83478,83480,83482,83484,83486,83488,83490],{"class":1088,"line":1276},[1086,83472,83473],{"class":1105},"            setattr",[1086,83475,1398],{"class":1146},[1086,83477,19494],{"class":1436},[1086,83479,1227],{"class":1146},[1086,83481,83125],{"class":1105},[1086,83483,1227],{"class":1146},[1086,83485,83107],{"class":1105},[1086,83487,1398],{"class":1146},[1086,83489,83136],{"class":1105},[1086,83491,4234],{"class":1146},[1086,83493,83494,83496],{"class":1088,"line":1282},[1086,83495,4239],{"class":1423},[1086,83497,83498],{"class":1436}," cls\n",[842,83500,83501],{},"The metaclass iterates through each MongoDB field name and creates a descriptor with that name, attaching it to the model class.",[1074,83503,83505],{"id":83504},"step-3-the-base-hybrid-model","Step 3: The Base Hybrid Model",[1013,83507,83509],{"className":1368,"code":83508,"language":1250,"meta":728,"style":728},"class HybridModel(models.Model, metaclass=HybridModelMeta):\n    class Meta:\n        abstract = True\n\n    _mongo_document = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        # Connect to the corresponding MongoDB document\n        self._mongo_document = self.get_mongo_collection().find_one(\n            {\"_id\": str(self.pk)}\n        )\n\n    def save(self, *args, **kwargs):\n        super().save(*args, **kwargs)\n        # Sync _m_ prefixed fields back to MongoDB\n        update_fields = {}\n        for field_name in self.mongo_fields:\n            private = f\"_m_{field_name}\"\n            if hasattr(self, private):\n                update_fields[field_name] = getattr(self, private)\n        if update_fields:\n            self.get_mongo_collection().update_one(\n                {\"_id\": str(self.pk)},\n                {\"$set\": update_fields},\n                upsert=True,\n            )\n",[895,83510,83511,83538,83547,83556,83560,83569,83573,83599,83622,83627,83651,83678,83682,83686,83710,83730,83735,83745,83761,83780,83798,83823,83832,83848,83873,83890,83898],{"__ignoreMap":728},[1086,83512,83513,83515,83518,83520,83522,83524,83526,83528,83531,83533,83536],{"class":1088,"line":1089},[1086,83514,4036],{"class":1155},[1086,83516,83517],{"class":1092}," HybridModel",[1086,83519,1398],{"class":1146},[1086,83521,21939],{"class":1092},[1086,83523,861],{"class":1146},[1086,83525,11370],{"class":1092},[1086,83527,1227],{"class":1146},[1086,83529,83530],{"class":1092}," metaclass",[1086,83532,1440],{"class":1146},[1086,83534,83535],{"class":1092},"HybridModelMeta",[1086,83537,4047],{"class":1146},[1086,83539,83540,83542,83545],{"class":1088,"line":729},[1086,83541,21963],{"class":1155},[1086,83543,83544],{"class":1092}," Meta",[1086,83546,1418],{"class":1146},[1086,83548,83549,83552,83554],{"class":1088,"line":1112},[1086,83550,83551],{"class":1436},"        abstract ",[1086,83553,1440],{"class":1146},[1086,83555,19535],{"class":1146},[1086,83557,83558],{"class":1088,"line":1181},[1086,83559,3390],{"emptyLinePlaceholder":738},[1086,83561,83562,83565,83567],{"class":1088,"line":1205},[1086,83563,83564],{"class":1436},"    _mongo_document ",[1086,83566,1440],{"class":1146},[1086,83568,20359],{"class":1146},[1086,83570,83571],{"class":1088,"line":1276},[1086,83572,3390],{"emptyLinePlaceholder":738},[1086,83574,83575,83577,83579,83581,83583,83585,83587,83589,83591,83594,83597],{"class":1088,"line":1282},[1086,83576,4065],{"class":1155},[1086,83578,83116],{"class":1105},[1086,83580,1398],{"class":1146},[1086,83582,4074],{"class":4073},[1086,83584,1227],{"class":1146},[1086,83586,47552],{"class":1146},[1086,83588,1210],{"class":1401},[1086,83590,1227],{"class":1146},[1086,83592,83593],{"class":1146}," **",[1086,83595,83596],{"class":1401},"kwargs",[1086,83598,4047],{"class":1146},[1086,83600,83601,83604,83606,83609,83612,83614,83616,83618,83620],{"class":1088,"line":1288},[1086,83602,83603],{"class":1092},"        super",[1086,83605,6813],{"class":1146},[1086,83607,83608],{"class":1105},"__init__",[1086,83610,83611],{"class":1146},"(*",[1086,83613,1210],{"class":1105},[1086,83615,1227],{"class":1146},[1086,83617,83593],{"class":1146},[1086,83619,83596],{"class":1105},[1086,83621,1455],{"class":1146},[1086,83623,83624],{"class":1088,"line":2685},[1086,83625,83626],{"class":1427},"        # Connect to the corresponding MongoDB document\n",[1086,83628,83629,83631,83633,83635,83637,83639,83641,83644,83646,83649],{"class":1088,"line":2700},[1086,83630,23875],{"class":1436},[1086,83632,861],{"class":1146},[1086,83634,83227],{"class":4109},[1086,83636,19552],{"class":1146},[1086,83638,23768],{"class":1436},[1086,83640,861],{"class":1146},[1086,83642,83643],{"class":1105},"get_mongo_collection",[1086,83645,6813],{"class":1146},[1086,83647,83648],{"class":1105},"find_one",[1086,83650,4094],{"class":1146},[1086,83652,83653,83656,83658,83661,83663,83665,83667,83669,83671,83673,83676],{"class":1088,"line":3398},[1086,83654,83655],{"class":1146},"            {",[1086,83657,1159],{"class":1146},[1086,83659,83660],{"class":1096},"_id",[1086,83662,1159],{"class":1146},[1086,83664,1133],{"class":1146},[1086,83666,1407],{"class":1092},[1086,83668,1398],{"class":1146},[1086,83670,4074],{"class":1436},[1086,83672,861],{"class":1146},[1086,83674,83675],{"class":4109},"pk",[1086,83677,20539],{"class":1146},[1086,83679,83680],{"class":1088,"line":1715},[1086,83681,4133],{"class":1146},[1086,83683,83684],{"class":1088,"line":3409},[1086,83685,3390],{"emptyLinePlaceholder":738},[1086,83687,83688,83690,83692,83694,83696,83698,83700,83702,83704,83706,83708],{"class":1088,"line":3415},[1086,83689,4065],{"class":1155},[1086,83691,25075],{"class":1105},[1086,83693,1398],{"class":1146},[1086,83695,4074],{"class":4073},[1086,83697,1227],{"class":1146},[1086,83699,47552],{"class":1146},[1086,83701,1210],{"class":1401},[1086,83703,1227],{"class":1146},[1086,83705,83593],{"class":1146},[1086,83707,83596],{"class":1401},[1086,83709,4047],{"class":1146},[1086,83711,83712,83714,83716,83718,83720,83722,83724,83726,83728],{"class":1088,"line":3421},[1086,83713,83603],{"class":1092},[1086,83715,6813],{"class":1146},[1086,83717,23200],{"class":1105},[1086,83719,83611],{"class":1146},[1086,83721,1210],{"class":1105},[1086,83723,1227],{"class":1146},[1086,83725,83593],{"class":1146},[1086,83727,83596],{"class":1105},[1086,83729,1455],{"class":1146},[1086,83731,83732],{"class":1088,"line":3427},[1086,83733,83734],{"class":1427},"        # Sync _m_ prefixed fields back to MongoDB\n",[1086,83736,83737,83740,83742],{"class":1088,"line":3433},[1086,83738,83739],{"class":1436},"        update_fields ",[1086,83741,1440],{"class":1146},[1086,83743,83744],{"class":1146}," {}\n",[1086,83746,83747,83749,83751,83753,83755,83757,83759],{"class":1088,"line":3439},[1086,83748,6903],{"class":1423},[1086,83750,83461],{"class":1436},[1086,83752,5931],{"class":1423},[1086,83754,23768],{"class":1436},[1086,83756,861],{"class":1146},[1086,83758,83448],{"class":4109},[1086,83760,1418],{"class":1146},[1086,83762,83763,83766,83768,83770,83772,83774,83776,83778],{"class":1088,"line":3444},[1086,83764,83765],{"class":1436},"            private ",[1086,83767,1440],{"class":1146},[1086,83769,4403],{"class":1155},[1086,83771,83157],{"class":1096},[1086,83773,4409],{"class":1187},[1086,83775,83136],{"class":1436},[1086,83777,4423],{"class":1187},[1086,83779,4441],{"class":1096},[1086,83781,83782,83784,83787,83789,83791,83793,83796],{"class":1088,"line":3450},[1086,83783,6918],{"class":1423},[1086,83785,83786],{"class":1105}," hasattr",[1086,83788,1398],{"class":1146},[1086,83790,4074],{"class":1436},[1086,83792,1227],{"class":1146},[1086,83794,83795],{"class":1105}," private",[1086,83797,4047],{"class":1146},[1086,83799,83800,83803,83805,83807,83809,83811,83813,83815,83817,83819,83821],{"class":1088,"line":3456},[1086,83801,83802],{"class":1436},"                update_fields",[1086,83804,4340],{"class":1146},[1086,83806,83136],{"class":1436},[1086,83808,4420],{"class":1146},[1086,83810,19552],{"class":1146},[1086,83812,6441],{"class":1105},[1086,83814,1398],{"class":1146},[1086,83816,4074],{"class":1436},[1086,83818,1227],{"class":1146},[1086,83820,83795],{"class":1105},[1086,83822,1455],{"class":1146},[1086,83824,83825,83827,83830],{"class":1088,"line":3462},[1086,83826,6800],{"class":1423},[1086,83828,83829],{"class":1436}," update_fields",[1086,83831,1418],{"class":1146},[1086,83833,83834,83837,83839,83841,83843,83846],{"class":1088,"line":3467},[1086,83835,83836],{"class":1436},"            self",[1086,83838,861],{"class":1146},[1086,83840,83643],{"class":1105},[1086,83842,6813],{"class":1146},[1086,83844,83845],{"class":1105},"update_one",[1086,83847,4094],{"class":1146},[1086,83849,83850,83853,83855,83857,83859,83861,83863,83865,83867,83869,83871],{"class":1088,"line":3473},[1086,83851,83852],{"class":1146},"                {",[1086,83854,1159],{"class":1146},[1086,83856,83660],{"class":1096},[1086,83858,1159],{"class":1146},[1086,83860,1133],{"class":1146},[1086,83862,1407],{"class":1092},[1086,83864,1398],{"class":1146},[1086,83866,4074],{"class":1436},[1086,83868,861],{"class":1146},[1086,83870,83675],{"class":4109},[1086,83872,20485],{"class":1146},[1086,83874,83875,83877,83879,83882,83884,83886,83888],{"class":1088,"line":3479},[1086,83876,83852],{"class":1146},[1086,83878,1159],{"class":1146},[1086,83880,83881],{"class":1096},"$set",[1086,83883,1159],{"class":1146},[1086,83885,1133],{"class":1146},[1086,83887,83829],{"class":1105},[1086,83889,17375],{"class":1146},[1086,83891,83892,83895],{"class":1088,"line":3485},[1086,83893,83894],{"class":1401},"                upsert",[1086,83896,83897],{"class":1146},"=True,\n",[1086,83899,83900],{"class":1088,"line":3491},[1086,83901,20080],{"class":1146},[842,83903,83904,83905,83908],{},"This abstract base class connects a MongoDB document to the model on initialization and automatically syncs ",[895,83906,83907],{},"_m_","-prefixed fields back to MongoDB on save.",[1074,83910,83912],{"id":83911},"step-4-the-product-model","Step 4: The Product Model",[1013,83914,83916],{"className":1368,"code":83915,"language":1250,"meta":728,"style":728},"class Product(HybridModel):\n    # Regular Django fields (relational)\n    category = models.ForeignKey(Category, on_delete=models.CASCADE)\n    created_at = models.DateTimeField(auto_now_add=True)\n\n    # Fields stored in MongoDB for fast reads\n    mongo_fields = [\"name\", \"description\", \"price\", \"attributes\", \"images\"]\n\n    @classmethod\n    def get_mongo_collection(cls):\n        return mongo_db[\"products\"]\n",[895,83917,83918,83932,83937,83968,83988,83992,83997,84048,84052,84058,84071],{"__ignoreMap":728},[1086,83919,83920,83922,83925,83927,83930],{"class":1088,"line":1089},[1086,83921,4036],{"class":1155},[1086,83923,83924],{"class":1092}," Product",[1086,83926,1398],{"class":1146},[1086,83928,83929],{"class":1092},"HybridModel",[1086,83931,4047],{"class":1146},[1086,83933,83934],{"class":1088,"line":729},[1086,83935,83936],{"class":1427},"    # Regular Django fields (relational)\n",[1086,83938,83939,83942,83944,83946,83948,83950,83952,83954,83956,83958,83960,83962,83964,83966],{"class":1088,"line":1112},[1086,83940,83941],{"class":1436},"    category ",[1086,83943,1440],{"class":1146},[1086,83945,22134],{"class":1436},[1086,83947,861],{"class":1146},[1086,83949,22139],{"class":1105},[1086,83951,1398],{"class":1146},[1086,83953,48471],{"class":1105},[1086,83955,1227],{"class":1146},[1086,83957,22148],{"class":1401},[1086,83959,1440],{"class":1146},[1086,83961,21939],{"class":1105},[1086,83963,861],{"class":1146},[1086,83965,22157],{"class":4109},[1086,83967,1455],{"class":1146},[1086,83969,83970,83973,83975,83977,83979,83981,83983,83986],{"class":1088,"line":1181},[1086,83971,83972],{"class":1436},"    created_at ",[1086,83974,1440],{"class":1146},[1086,83976,22134],{"class":1436},[1086,83978,861],{"class":1146},[1086,83980,22508],{"class":1105},[1086,83982,1398],{"class":1146},[1086,83984,83985],{"class":1401},"auto_now_add",[1086,83987,22252],{"class":1146},[1086,83989,83990],{"class":1088,"line":1205},[1086,83991,3390],{"emptyLinePlaceholder":738},[1086,83993,83994],{"class":1088,"line":1276},[1086,83995,83996],{"class":1427},"    # Fields stored in MongoDB for fast reads\n",[1086,83998,83999,84002,84004,84006,84008,84010,84012,84014,84016,84018,84020,84022,84024,84027,84029,84031,84033,84035,84037,84039,84041,84044,84046],{"class":1088,"line":1282},[1086,84000,84001],{"class":1436},"    mongo_fields ",[1086,84003,1440],{"class":1146},[1086,84005,1217],{"class":1146},[1086,84007,1159],{"class":1146},[1086,84009,4184],{"class":1096},[1086,84011,1159],{"class":1146},[1086,84013,1227],{"class":1146},[1086,84015,1195],{"class":1146},[1086,84017,15465],{"class":1096},[1086,84019,1159],{"class":1146},[1086,84021,1227],{"class":1146},[1086,84023,1195],{"class":1146},[1086,84025,84026],{"class":1096},"price",[1086,84028,1159],{"class":1146},[1086,84030,1227],{"class":1146},[1086,84032,1195],{"class":1146},[1086,84034,21592],{"class":1096},[1086,84036,1159],{"class":1146},[1086,84038,1227],{"class":1146},[1086,84040,1195],{"class":1146},[1086,84042,84043],{"class":1096},"images",[1086,84045,1159],{"class":1146},[1086,84047,1273],{"class":1146},[1086,84049,84050],{"class":1088,"line":1288},[1086,84051,3390],{"emptyLinePlaceholder":738},[1086,84053,84054,84056],{"class":1088,"line":2685},[1086,84055,6734],{"class":1146},[1086,84057,19482],{"class":1092},[1086,84059,84060,84062,84065,84067,84069],{"class":1088,"line":2700},[1086,84061,4065],{"class":1155},[1086,84063,84064],{"class":1105}," get_mongo_collection",[1086,84066,1398],{"class":1146},[1086,84068,19494],{"class":1401},[1086,84070,4047],{"class":1146},[1086,84072,84073,84075,84078,84080,84082,84084,84086],{"class":1088,"line":3398},[1086,84074,4239],{"class":1423},[1086,84076,84077],{"class":1436}," mongo_db",[1086,84079,4340],{"class":1146},[1086,84081,1159],{"class":1146},[1086,84083,41981],{"class":1096},[1086,84085,1159],{"class":1146},[1086,84087,1273],{"class":1146},[842,84089,84090],{},"The implementation is straightforward: define a MongoDB document containing product fields and connect it to the Product model. The model can still use Django relationships normally.",[863,84092,84094],{"id":84093},"performance-comparison","Performance Comparison",[842,84096,84097,84098,84103],{},"We created an endpoint to retrieve products within a specific category and benchmarked it using the ",[846,84099,84102],{"href":84100,"rel":84101},"https://github.com/hatoo/oha",[850],"OHA"," tool — 10,000 requests across 50 threads.",[1045,84105,84107,84111],{"className":84106},[1048,1049,1765,1051,1052],[1054,84108],{"description":84109,"title":84110},"- Avg response time: ~120ms - Requests/sec: ~400 - P99 latency: ~250ms","Standard Django (PostgreSQL only)",[1054,84112],{"description":84113,"title":84114},"- Avg response time: ~14ms - Requests/sec: ~3,500 - P99 latency: ~30ms","Hybrid Model (PostgreSQL + MongoDB)",[1032,84116,84117],{},[842,84118,84119,84120,84123],{},"The hybrid model showed an ",[996,84121,84122],{},"8–10x speed improvement"," over the standard Django implementation for read-heavy catalog queries.",[863,84125,18681],{"id":18680},[842,84127,84128],{},"The hybrid model approach significantly enhanced performance by offloading read-heavy product data to MongoDB while keeping relational integrity in PostgreSQL. There's room for further improvement — loading all MongoDB fields at once, batch-loading documents for entire querysets, and adding caching.",[842,84130,84131],{},"This approach offers a practical balance between relational and non-relational databases while staying fully compatible with Django Admin and Django REST Framework.",[1680,84133,84134],{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .s5tWE, html code.shiki .s5tWE{--shiki-light:#E53935;--shiki-light-font-style:italic;--shiki-default:#F07178;--shiki-default-font-style:italic;--shiki-dark:#F07178;--shiki-dark-font-style:italic}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 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 .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 .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);}",{"title":728,"searchDepth":729,"depth":729,"links":84136},[84137,84138,84144,84145],{"id":27939,"depth":729,"text":27940},{"id":52066,"depth":729,"text":27663,"children":84139},[84140,84141,84142,84143],{"id":83094,"depth":1112,"text":83095},{"id":83339,"depth":1112,"text":83340},{"id":83504,"depth":1112,"text":83505},{"id":83911,"depth":1112,"text":83912},{"id":84093,"depth":729,"text":84094},{"id":18680,"depth":729,"text":18681},"2023-11-06T00:00:00.000Z","How combining relational and non-relational databases in Django solved performance bottlenecks for a high-traffic e-commerce catalog microservice.",{"src":84149},"/images/blog/musictechlab_blog_hybrid-database-model-in-django-as-a-performance-booster.webp",{"enabled":738,"items":84151},[84152,84154,84156],{"text":84153,"icon":2939},"Hybrid model achieved 8-10x speed improvement over standard Django for read-heavy queries.",{"text":84155,"icon":31808},"Average response time dropped from 120ms to 14ms by offloading product fields to MongoDB.",{"text":84157,"icon":5365},"Descriptors and a metaclass keep the approach fully compatible with Django Admin and DRF.",{},{"title":152,"description":84147},[18784,731,4987,84161],"mongodb","FusBV0SAjMJK8iTHoIGSjldwjXeuRCOnTQg-203Hs_A",{"id":84164,"title":514,"authors":84165,"badge":723,"body":84168,"category":756,"client":723,"date":84224,"description":84225,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":84226,"keyTakeaways":84228,"meta":84238,"navigation":738,"path":515,"seo":84239,"status":723,"stem":516,"tags":84240,"teaser":723,"__hash__":84241},"posts/blog/software-development/how-to-handle-high-loads-on-e-commerce-platform-with-ease.md",[84166],{"name":83061,"avatar":84167},{"src":83063},{"type":725,"value":84169,"toc":84217},[84170,84172,84176,84179,84183,84186,84189,84192,84195,84198,84201,84204,84207,84210,84212,84215],[842,84171,40867],{},[863,84173,84175],{"id":84174},"frameworks","Frameworks",[842,84177,84178],{},"Currently, for our specific use cases, we have implemented two distinct frameworks for our microservices: Django and FastAPI. We employ Django for services that do not experience high loads and require the convenience of Django's built-in admin features. Conversely, FastAPI is our framework of choice for services where the primary focus is achieving rapid response times.",[863,84180,84182],{"id":84181},"databases","Databases",[842,84184,84185],{},"When it comes to managing the relational databases for our microservices, we've chosen MariaDB, a time-tested, dependable database system that enjoys widespread adoption across the industry. To maintain optimal separation and scalability, we've established dedicated instances of MariaDB for each microservice. This strategy guarantees superior isolation and scalability, affording each microservice its own distinct database for efficient data storage and management.",[842,84187,84188],{},"In addition to MariaDB, for our NoSQL requirements, we've embraced MongoDB as our solution of choice. Similar to our approach with MariaDB, we've implemented separate MongoDB instances for each microservice, ensuring the flexibility and tailored data management capabilities that each microservice demands.",[863,84190,72282],{"id":84191},"communication",[842,84193,84194],{},"For facilitating internal microservice communication, we rely on the NATS messaging system, renowned for its exceptional speed and robustness. When it comes to interfacing with the external world, we opt for the REST protocol, ensuring seamless and standardized communication with external services and clients.",[863,84196,84197],{"id":18784},"Development",[842,84199,84200],{},"As our project continues to expand, we have implemented a valuable tool called \"cookiecutter\" to streamline the process of creating new microservices from standardized templates. Cookiecutter accelerates project initiation by swiftly generating new projects from pre-defined templates, a functionality made accessible through a command-line utility. This tool is particularly well-suited for producing Python package projects and more, enhancing our development efficiency.",[842,84202,84203],{},"Thanks to Cookiecutter, the creation of new services has become a breeze. With just a simple command, such as make create-django-app or make create-fastapi-app we can instantly initiate new microservices based on our preferred Django or FastAPI templates, saving valuable time and ensuring consistency across our expanding ecosystem.",[842,84205,84206],{},"As our entire application is designed to operate within a Kubernetes cluster, it's essential to have a seamless local development solution that eliminates the complexities involved. Ideally, we aim for a system that not only simplifies local development but also automates processes such as rebuilding and running the application. To meet these objectives, we've adopted the \"Tilt\" tool.",[842,84208,84209],{},"Tilt serves as a comprehensive solution, automating every step from the moment code changes are detected to the instantiation of a new process. This encompasses tasks like file monitoring, container image building, and keeping your development environment in sync with the latest code changes. With Tilt in place, our development workflow becomes significantly more efficient, allowing our team to focus on coding and innovation without being bogged down by manual tasks.",[863,84211,18681],{"id":18680},[842,84213,84214],{},"Anqa Commerce's tech stack, built on these foundations, stands as a testament to the platform's dedication to providing a dynamic, efficient, and scalable e-commerce solution, ultimately enhancing the shopping experience for both customers and vendors.",[842,84216,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":84218},[84219,84220,84221,84222,84223],{"id":84174,"depth":729,"text":84175},{"id":84181,"depth":729,"text":84182},{"id":84191,"depth":729,"text":72282},{"id":18784,"depth":729,"text":84197},{"id":18680,"depth":729,"text":18681},"2023-11-03T00:00:00.000Z","How Anqa Commerce uses a microservice architecture with Django, FastAPI, MariaDB, MongoDB, and NATS to handle high-traffic e-commerce workloads at scale.",{"src":84227},"/images/blog/musictechlab_blog_how-to-handle-high-loads-on-e-commerce-platform-with-ease.webp",{"enabled":738,"items":84229},[84230,84232,84234,84236],{"text":84231,"icon":2939},"FastAPI handles high-load services while Django serves admin-heavy, lower-traffic microservices.",{"text":84233,"icon":2895},"Separate MariaDB and MongoDB instances per microservice ensure isolation and scalability.",{"text":84235,"icon":72284},"NATS messaging handles internal communication; REST serves external API consumers.",{"text":84237,"icon":8737},"Cookiecutter templates and Tilt streamline new microservice creation and local Kubernetes development.",{},{"title":514,"description":84225},[52276,15279],"7-lq4WYPbdQA-TkqCxZBkGSGP183sH_o5ZlUMHiJol0",{"id":84243,"title":670,"authors":84244,"badge":723,"body":84247,"category":756,"client":723,"date":84521,"description":84522,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":84523,"keyTakeaways":84525,"meta":84533,"navigation":738,"path":671,"seo":84534,"status":723,"stem":672,"tags":84535,"teaser":723,"__hash__":84536},"posts/blog/software-development/what-is-discovery-document.md",[84245],{"name":834,"to":720,"avatar":84246},{"src":722},{"type":725,"value":84248,"toc":84505},[84249,84252,84270,84272,84276,84280,84295,84299,84315,84319,84336,84340,84355,84359,84375,84379,84393,84397,84413,84417,84432,84436,84447,84451,84464,84466,84470,84475,84482,84487,84489,84493],[842,84250,84251],{},"A Discovery Document gathers and documents essential information so that the project team, stakeholders, and relevant parties share a clear, common understanding of what needs to be accomplished — and how to get there.",[1045,84253,84255,84259,84263,84266],{"className":84254},[1048,50574,50605,1051,1052],[1054,84256],{"description":84257,"icon":66018,"title":84258},"Get everyone on the same page before a single line of code is written.","Align",[1054,84260],{"description":84261,"icon":1057,"title":84262},"Surface unknowns, constraints, and dependencies early.","De-risk",[1054,84264],{"description":84265,"icon":3181,"title":34627},"Define scope boundaries so the team builds the right thing.",[1054,84267],{"description":84268,"icon":11617,"title":84269},"Create a shared artifact that stakeholders can sign off on.","Commit",[4937,84271],{},[863,84273,84275],{"id":84274},"what-goes-into-a-discovery-document","What Goes Into a Discovery Document?",[1074,84277,84279],{"id":84278},"_1-introduction","1. Introduction",[1045,84281,84283,84286,84291],{"className":84282},[1048,1049,1050,1051,1052],[1054,84284],{"description":84285,"icon":3850,"title":3021},"A brief overview of the document's intent and what the discovery process aims to achieve.",[1054,84287],{"description":84288,"icon":84289,"title":84290},"Define the boundaries and limitations of the discovery process.","i-lucide-scan","Scope",[1054,84292],{"description":84293,"icon":4845,"title":84294},"List the key individuals or groups involved in the project.","Stakeholders",[1074,84296,84298],{"id":84297},"_2-projectproduct-overview","2. Project/Product Overview",[1045,84300,84302,84306,84311],{"className":84301},[1048,1049,1050,1051,1052],[1054,84303],{"description":84304,"icon":8790,"title":84305},"Provide a concise history or context for the project/product.","Background",[1054,84307],{"description":84308,"icon":84309,"title":84310},"Clearly state the primary objectives and desired outcomes.","i-lucide-flag","Goals & Objectives",[1054,84312],{"description":84313,"icon":10530,"title":84314},"Define the metrics or criteria that will determine project success.","Success Criteria",[1074,84316,84318],{"id":84317},"_3-stakeholder-analysis","3. Stakeholder Analysis",[1045,84320,84322,84327,84332],{"className":84321},[1048,1049,1050,1051,1052],[1054,84323],{"description":84324,"icon":84325,"title":84326},"Identify all individuals, teams, or organizations with a vested interest.","i-lucide-user-check","Key Stakeholders",[1054,84328],{"description":84329,"icon":84330,"title":84331},"Describe the roles and responsibilities of each stakeholder.","i-lucide-clipboard-list","Roles & Responsibilities",[1054,84333],{"description":84334,"icon":52136,"title":84335},"Outline how and when stakeholders will be engaged and updated.","Communication Plan",[1074,84337,84339],{"id":84338},"_4-problem-statement","4. Problem Statement",[1045,84341,84343,84347,84351],{"className":84342},[1048,1049,1050,1051,1052],[1054,84344],{"description":84345,"icon":3847,"title":84346},"Identify the issues or problems that the project aims to address.","Current Challenges",[1054,84348],{"description":84349,"icon":50270,"title":84350},"Highlight the opportunities that can be leveraged.","Opportunities",[1054,84352],{"description":84353,"icon":7498,"title":84354},"Specify any limitations or constraints that need to be considered.","Constraints",[1074,84356,84358],{"id":84357},"_5-user-needs-requirements","5. User Needs & Requirements",[1045,84360,84362,84367,84371],{"className":84361},[1048,1049,1050,1051,1052],[1054,84363],{"description":84364,"icon":84365,"title":84366},"Create detailed user personas that represent the target audience.","i-lucide-user","User Personas",[1054,84368],{"description":84369,"icon":4830,"title":84370},"Describe specific scenarios to understand user needs and behavior.","User Stories",[1054,84372],{"description":84373,"icon":50622,"title":84374},"List the essential functionalities and features required.","Functional Requirements",[1074,84376,84378],{"id":84377},"_6-technical-requirements","6. Technical Requirements",[1045,84380,84382,84385,84389],{"className":84381},[1048,1049,1050,1051,1052],[1054,84383],{"description":84384,"icon":6395,"title":65410},"Document the technical infrastructure, platforms, and tools to be used.",[1054,84386],{"description":84387,"icon":7495,"title":84388},"Specify security and compliance requirements.","Security",[1054,84390],{"description":84391,"icon":52124,"title":84392},"Identify external systems or services that need to be integrated.","Integration Points",[1074,84394,84396],{"id":84395},"_7-project-timeline","7. Project Timeline",[1045,84398,84400,84405,84409],{"className":84399},[1048,1049,1050,1051,1052],[1054,84401],{"description":84402,"icon":84403,"title":84404},"Define key project milestones and their estimated dates.","i-lucide-milestone","Milestones",[1054,84406],{"description":84407,"icon":1779,"title":84408},"List any dependencies that could impact the project timeline.","Dependencies",[1054,84410],{"description":84411,"icon":65185,"title":84412},"Describe the allocation of team members and budget over time.","Resource Allocation",[1074,84414,84416],{"id":84415},"_8-risk-assessment","8. Risk Assessment",[1045,84418,84420,84424,84428],{"className":84419},[1048,1049,1050,1051,1052],[1054,84421],{"description":84422,"icon":7560,"title":84423},"Identify potential risks that could affect the project.","Risk Identification",[1054,84425],{"description":84426,"icon":3844,"title":84427},"Evaluate the impact and likelihood of each risk.","Risk Analysis",[1054,84429],{"description":84430,"icon":15173,"title":84431},"Outline strategies to mitigate or manage identified risks.","Risk Mitigation",[1074,84433,84435],{"id":84434},"_9-budget-resources","9. Budget & Resources",[1045,84437,84439,84443],{"className":84438},[1048,1049,1765,1051,1052],[1054,84440],{"description":84441,"icon":1774,"title":84442},"Detail the budget allocation for the project, including cost estimates.","Budget Plan",[1054,84444],{"description":84445,"icon":5340,"title":84446},"Specify the human and material resources needed.","Resource Plan",[1074,84448,84450],{"id":84449},"_10-next-steps-sign-off","10. Next Steps & Sign-off",[1045,84452,84454,84459],{"className":84453},[1048,1049,1765,1051,1052],[1054,84455],{"description":84456,"icon":84457,"title":84458},"A step-by-step plan for moving forward after the discovery phase, including key deliverables.","i-lucide-arrow-right","Action Plan",[1054,84460],{"description":84461,"icon":84462,"title":84463},"Obtain approval or feedback from relevant stakeholders to proceed to development.","i-lucide-pen-tool","Sign-off",[4937,84465],{},[863,84467,84469],{"id":84468},"why-discovery-documents-matter","Why Discovery Documents Matter",[1901,84471,84472],{},[842,84473,84474],{},"Discovery Documents are particularly important for complex projects. Understanding requirements and constraints at the outset can significantly impact success — reducing misunderstandings, scope creep, and risk.",[842,84476,84477,84478,84481],{},"Discovery Documents are ",[996,84479,84480],{},"living documents"," — they may be updated as the project progresses and more information becomes available. They help ensure that all stakeholders share a vision of the project from day one.",[1032,84483,84484],{},[842,84485,84486],{},"At MusicTech Lab, we use Discovery Documents at the start of every major project to align expectations, reduce risk, and set teams up for success.",[4937,84488],{},[863,84490,84492],{"id":84491},"get-the-template","Get the Template",[1045,84494,84496,84501],{"className":84495},[13033,50238,50239,1052],[50241,84497],{"color":50243,"icon":84498,"label":84499,"to":84500,"variant":50246,"target":50245},"i-lucide-download","Download Discovery Doc Template","/images/blog/musictechlab_blog_discovery-doc-template.pdf",[50241,84502],{"color":50243,"icon":72284,"label":84503,"to":3802,"variant":84504},"Run a Discovery with Us","outline",{"title":728,"searchDepth":729,"depth":729,"links":84506},[84507,84519,84520],{"id":84274,"depth":729,"text":84275,"children":84508},[84509,84510,84511,84512,84513,84514,84515,84516,84517,84518],{"id":84278,"depth":1112,"text":84279},{"id":84297,"depth":1112,"text":84298},{"id":84317,"depth":1112,"text":84318},{"id":84338,"depth":1112,"text":84339},{"id":84357,"depth":1112,"text":84358},{"id":84377,"depth":1112,"text":84378},{"id":84395,"depth":1112,"text":84396},{"id":84415,"depth":1112,"text":84416},{"id":84434,"depth":1112,"text":84435},{"id":84449,"depth":1112,"text":84450},{"id":84468,"depth":729,"text":84469},{"id":84491,"depth":729,"text":84492},"2023-10-30T00:00:00.000Z","What is a Discovery Document and why it matters. Learn how to structure project requirements, define scope, and align stakeholders before development begins.",{"src":84524},"/images/blog/musictechlab_blog_what-is-discovery-document.webp",{"enabled":738,"items":84526},[84527,84529,84531],{"text":84528,"icon":11617},"A Discovery Document aligns all stakeholders on scope, risks, and goals before coding starts.",{"text":84530,"icon":1769},"It covers 10 sections from problem statement and user personas to risk assessment and sign-off.",{"text":84532,"icon":50270},"Discovery Documents are living artifacts that reduce scope creep and misunderstandings.",{},{"title":670,"description":84522},[74615],"7QTn0udgudjiOTrBp-xldFi4cyIGiplMmkMe5MsVei8",{"id":84538,"title":678,"authors":84539,"badge":723,"body":84542,"category":756,"client":723,"date":84727,"description":84728,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":84729,"keyTakeaways":84731,"meta":84739,"navigation":738,"path":679,"seo":84740,"status":723,"stem":680,"tags":84741,"teaser":723,"__hash__":84742},"posts/blog/software-development/what-is-watermarked-song.md",[84540],{"name":834,"to":720,"avatar":84541},{"src":722},{"type":725,"value":84543,"toc":84720},[84544,84547,84554,84558,84577,84581,84584,84604,84607,84618,84621,84647,84660,84664,84667,84700,84713,84717],[842,84545,84546],{},"A watermarked song contains a hidden, inaudible signal embedded in the audio data. This signal acts as a digital fingerprint — it identifies the track, its owner, and sometimes the specific recipient who received the file. The watermark is designed to survive common transformations like compression, format conversion, and even re-recording.",[842,84548,84549,84550,84553],{},"Unlike visible watermarks on images, audio watermarks are ",[996,84551,84552],{},"imperceptible to the listener",". The audio quality stays intact, but specialized software can detect and decode the embedded data when needed.",[863,84555,84557],{"id":84556},"why-watermarks-matter","Why Watermarks Matter",[1045,84559,84561,84565,84569,84573],{"className":84560},[1048,1049,1765,1051,1052],[1054,84562],{"description":84563,"title":84564},"Labels, artists, and publishers embed watermarks to deter unauthorized distribution. If a track leaks, the watermark traces it back to the source.","Copyright Protection",[1054,84566],{"description":84567,"title":84568},"When music is licensed for films, ads, or TV shows, watermarks ensure the right rights holders get compensated. They act as proof of origin.","Ownership & Licensing",[1054,84570],{"description":84571,"title":84572},"If a watermarked pre-release track appears on piracy sites, the embedded data identifies exactly who received that copy — narrowing down the leak.","Anti-Piracy",[1054,84574],{"description":84575,"title":84576},"Streaming platforms and content distributors scan uploaded files for watermarks to detect copyright infringement and enforce licensing agreements automatically.","Monitoring & Detection",[863,84578,84580],{"id":84579},"how-audio-watermarking-works","How Audio Watermarking Works",[842,84582,84583],{},"The basic process involves embedding a signal into the audio that is:",[991,84585,84586,84592,84598],{},[961,84587,84588,84591],{},[996,84589,84590],{},"Inaudible"," — the listener can't tell the difference between a watermarked and unwatermarked version",[961,84593,84594,84597],{},[996,84595,84596],{},"Robust"," — the signal survives MP3 compression, re-encoding, cropping, and even analog re-recording",[961,84599,84600,84603],{},[996,84601,84602],{},"Detectable"," — specialized software can extract the watermark and read its payload (owner ID, timestamp, recipient)",[842,84605,84606],{},"There are two main approaches:",[1045,84608,84610,84614],{"className":84609},[1048,1049,1765,1051,1052],[1054,84611],{"description":84612,"title":84613},"The watermark is spread across the audio spectrum, making it nearly impossible to remove without destroying the audio quality. Used by most commercial systems.","Spread Spectrum",[1054,84615],{"description":84616,"title":84617},"The watermark is hidden in parts of the audio that the human ear can't perceive — exploiting the same principles that make MP3 compression work.","Psychoacoustic Masking",[863,84619,84620],{"id":11447},"Real-World Use Cases",[958,84622,84623,84629,84635,84641],{},[961,84624,84625,84628],{},[996,84626,84627],{},"Pre-release distribution"," — labels send watermarked copies to press and radio. Each copy has a unique watermark tied to the recipient. If the track leaks before release, the source is identified within minutes.",[961,84630,84631,84634],{},[996,84632,84633],{},"Sync licensing"," — when a song is placed in a commercial or film, the watermark confirms which version was used and who holds the rights.",[961,84636,84637,84640],{},[996,84638,84639],{},"Streaming royalties"," — some platforms use fingerprinting (a related technology) alongside watermarks to match plays to rights holders and calculate royalties accurately.",[961,84642,84643,84646],{},[996,84644,84645],{},"Content ID systems"," — platforms like YouTube use audio fingerprinting to detect copyrighted content in user uploads and route revenue to the correct rights holders.",[1572,84648,84649],{},[842,84650,84651,84652,84655,84656,84659],{},"Audio watermarking and audio fingerprinting are related but different. Watermarking ",[996,84653,84654],{},"embeds"," data into the audio file. Fingerprinting ",[996,84657,84658],{},"generates"," a unique signature from the audio for matching purposes. Both are used in modern music rights management.",[863,84661,84663],{"id":84662},"tools-and-services","Tools and Services",[842,84665,84666],{},"Several companies provide commercial audio watermarking solutions:",[958,84668,84669,84677,84685,84693],{},[961,84670,84671,84676],{},[846,84672,84675],{"href":84673,"rel":84674},"https://www.digimarc.com/",[850],"Digimarc"," — enterprise-grade watermarking for audio and other media",[961,84678,84679,84684],{},[846,84680,84683],{"href":84681,"rel":84682},"https://www.audiblemagic.com/",[850],"Audible Magic"," — content identification and compliance",[961,84686,84687,84692],{},[846,84688,84691],{"href":84689,"rel":84690},"https://www.kantar.com/",[850],"Civolution (now part of Kantar)"," — broadcast monitoring with audio watermarks",[961,84694,84695,84699],{},[846,84696,78560],{"href":84697,"rel":84698},"https://www.bmat.com/",[850]," — music monitoring and identification used by collecting societies worldwide",[842,84701,84702,84703,1589,84708,84712],{},"For open-source experimentation, Python libraries like ",[846,84704,84707],{"href":84705,"rel":84706},"https://github.com/jiaaro/pydub",[850],"pydub",[846,84709,31979],{"href":84710,"rel":84711},"https://librosa.org/",[850]," can be used to explore basic watermarking concepts, though production-grade watermarking requires specialized algorithms.",[863,84714,84716],{"id":84715},"key-takeaway","Key Takeaway",[842,84718,84719],{},"Watermarked songs are a critical layer of protection in the digital music supply chain. They're invisible to listeners but provide traceable proof of ownership, licensing, and distribution — making them essential for anyone working in music rights, distribution, or anti-piracy.",{"title":728,"searchDepth":729,"depth":729,"links":84721},[84722,84723,84724,84725,84726],{"id":84556,"depth":729,"text":84557},{"id":84579,"depth":729,"text":84580},{"id":11447,"depth":729,"text":84620},{"id":84662,"depth":729,"text":84663},{"id":84715,"depth":729,"text":84716},"2023-09-07T00:00:00.000Z","What are watermarked songs and why do they matter? Learn how audio watermarks protect copyright, track ownership, prevent piracy, and authenticate music.",{"src":84730},"/images/blog/musictechlab_blog_what-is-watermarked-song.webp",{"enabled":738,"items":84732},[84733,84735,84737],{"text":84734,"icon":9547},"Audio watermarks are inaudible signals that survive compression, re-encoding, and even re-recording.",{"text":84736,"icon":7495},"If a pre-release track leaks, the unique watermark identifies the exact recipient within minutes.",{"text":84738,"icon":8500},"Spread spectrum and psychoacoustic masking are the two main watermarking techniques in use.",{},{"title":678,"description":84728},[5523,26062],"5lSYUc9pqJwmtnCuasZH-PkPdCdUhMqyWZ3gnhh8JEM",{"id":84744,"title":418,"authors":84745,"badge":723,"body":84748,"category":756,"client":723,"date":85092,"description":85093,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":85094,"keyTakeaways":85096,"meta":85106,"navigation":738,"path":419,"seo":85107,"status":723,"stem":420,"tags":85108,"teaser":723,"__hash__":85109},"posts/blog/software-development/d-commerce-decoded-cutting-through-the-hype.md",[84746],{"name":74344,"avatar":84747},{"src":74346},{"type":725,"value":84749,"toc":85071},[84750,84752,84755,84757,84760,84762,84765,84767,84771,84773,84776,84778,84781,84783,84786,84788,84792,84795,84797,84801,84803,84806,84808,84811,84813,84816,84818,84821,84823,84827,84830,84832,84835,84837,84840,84842,84845,84847,84851,84853,84856,84858,84861,84863,84866,84868,84871,84873,84876,84878,84882,84885,84887,84890,84892,84895,84897,84900,84902,84905,84907,84911,84914,84916,84920,84922,84925,84927,84930,84932,84936,84938,84941,84943,84946,84948,84952,84954,84957,84959,84962,84964,84968,84970,84973,84975,84979,84981,84984,84986,84989,84991,84994,84996,85000,85002,85005,85007,85010,85012,85015,85017,85021,85023,85026,85028,85031,85033,85036,85038,85042,85044,85047,85049,85052,85055,85059,85062,85065,85068],[842,84751,40867],{},[842,84753,84754],{},"In the beginning, there was barter: a chicken for your two bushels of wheat. Then, as our societies advanced, so did our means of trade. Physical currency, paper notes, checks, credit cards, and eventually, digital transactions. In the course of this progression, one particular type of commerce has emerged, waving hello from the horizon of our digital future. This new kid on the block is known as d-commerce, the next evolutionary stage after e-commerce. Yet, what exactly is d-commerce, and why does it hold such a pivotal role in the economy of tomorrow?",[842,84756,52316],{},[842,84758,84759],{},"D-commerce, or digital commerce, is the latest stop on this evolutionary journey. It marks the increasing role of digital goods and services in our lives - from streaming subscriptions, to software licenses, to digital assets in video games. But it's not just about consumers binge-watching their favourite shows or businesses running on cloud platforms. It’s about the major shift we're witnessing in how we perceive and value 'ownership' in the digital age.",[842,84761,52316],{},[842,84763,84764],{},"So, hold onto your hats, folks. We're embarking on a thrilling expedition into the dynamic world of d-commerce - a place where the lines between the physical and virtual blur, and where our concept of trade is being reimagined. This digital revolution might feel overwhelming at times, but fear not - this blog post is your trusty guide, leading you through the ins and outs of this brave new world.",[842,84766,52316],{},[863,84768,84770],{"id":84769},"d-commerce-decoded-a-look-beyond-the-buzzword","D-Commerce Decoded: A Look Beyond the Buzzword",[842,84772,52316],{},[842,84774,84775],{},"So, what is d-commerce exactly? It's the evolving realm of digital commerce, specifically focused on the trading of digital goods and services. We're not talking about physical products bought online, but rather, those intangible items like eBooks, digital music, online courses, streaming services, and downloadable software. Essentially, if it's a virtual good or service sold online, it falls into the d-commerce domain.",[842,84777,52316],{},[842,84779,84780],{},"Whether you're a software developer, movie studio, music producer, or online educator, this shift forces you to rethink your business strategies and adapt to a digital marketplace that is as challenging as it is rewarding. It's not just about selling; it's about understanding that in the world of d-commerce, the relationship between the provider and the consumer is ongoing and constantly evolving.",[842,84782,52316],{},[842,84784,84785],{},"These industries face the complex task of managing digital transactions, payments, and subscriptions effectively. To navigate this, companies are required to understand the nuances of d-commerce and maintain flexible, efficient delivery and management platforms. For D-commerce isn't just about selling digital goods or services, but above all, about providing a streamlined, exceptional customer experience, which requires holistic approach towards business.",[842,84787,52316],{},[863,84789,84791],{"id":84790},"the-tech-powering-d-commerce-a-reality-check","The Tech Powering D-Commerce: A Reality Check",[842,84793,84794],{},"As we journey further into the world of d-commerce, it's crucial to understand the technology that fuels this digital powerhouse. This isn't some abstract sci-fi concept; it's a pragmatic reality rooted in advanced technology that makes buying and selling digital goods possible. Let's break down the tech players on this digital field: Content Delivery Networks (CDNs), Digital Rights Management (DRM), e-commerce platforms, and payment gateways.",[842,84796,52316],{},[1074,84798,84800],{"id":84799},"e-commerce-platforms-engineering-the-digital-marketplace","E-Commerce Platforms: Engineering the Digital Marketplace",[842,84802,52316],{},[842,84804,84805],{},"E-commerce platforms act as the sophisticated hub orchestrating digital transactions within the d-commerce sphere. Under the hood, these platforms employ a blend of robust technologies to support the flow of digital goods.",[842,84807,52316],{},[842,84809,84810],{},"Backend technologies form the operational core of these platforms. Database systems, often based on SQL or NoSQL paradigms, are designed to store and manage vast amounts of product information, user details, and transaction records. Server-side languages such as Python, Ruby, Java, or Node.js handle business logic, processing user requests and interactions, and driving the core functionalities of the platform, including subsystems designed specifically for digital goods, such as digital inventory management, shopping cart systems, digital download modules, and license key distribution mechanisms.",[842,84812,52316],{},[842,84814,84815],{},"APIs (Application Programming Interfaces) are another crucial component. They allow seamless integration between the e-commerce platform and other systems like CDNs, DRMs, and payment gateways. Well-defined APIs provide a systematic way for these disparate systems to exchange data and coordinate operations.",[842,84817,52316],{},[842,84819,84820],{},"On the user-facing end, front-end technologies are used to create interactive, responsive, and accessible interfaces. Leveraging browser languages, HTML, CSS, JavaScript, and modern frameworks like React or Vue.js, developers craft intuitive interfaces that make navigating the platform a smooth, user-friendly experience.",[842,84822,52316],{},[1074,84824,84826],{"id":84825},"digital-rights-management-fight-against-the-piracy","Digital Rights Management: Fight Against the Piracy",[842,84828,84829],{},"Digital Rights Management (DRM) systems while being hated by most users, are necessary for real digital asset economy, serving as the safeguard for intellectual property. They implement advanced cryptographic methodologies to ensure that digital content remains accessible only to authorized parties.",[842,84831,52316],{},[842,84833,84834],{},"Here symmetric or asymmetric encryption algorithms are used to transform the digital content into an unreadable format. The key used for this encryption process is securely stored and only released to authorized entities. This process effectively renders the digital content inaccessible and unusable without the appropriate decryption key.",[842,84836,52316],{},[842,84838,84839],{},"Additionally, DRM systems might use digital signatures, a form of asymmetric cryptography, to verify the authenticity and security of the digital content. The content provider signs the digital goods using a private key, and customers can verify the signature with the provider's public key. This process ensures the integrity of the digital content and ascertains that it has not been tampered with since it was signed, protecting users against malware distribution.",[842,84841,52316],{},[842,84843,84844],{},"On top of encryption and digital signatures, DRM systems employ license servers to manage the rights of the digital content, providing an additional layer of control. These licenses, delivered to the purchaser upon acquiring the content, stipulate usage restrictions such as the number of devices that can access the content, time-based access limitations, or geographically enforced rules.",[842,84846,52316],{},[1074,84848,84850],{"id":84849},"content-delivery-networks-high-speed-data-distribution","Content Delivery Networks: High-Speed Data Distribution",[842,84852,52316],{},[842,84854,84855],{},"Content Delivery Networks (CDNs) role is to support high-speed content delivery. These  complex, distributed networks of servers are strategically dispersed across the globe, and designed to efficiently transmit digital data to end-users. Their architecture and design principles revolve around proximity, redundancy, and performance optimization.",[842,84857,52316],{},[842,84859,84860],{},"To minimize latency, CDNs employ the principle of geographic proximity, using edge servers located in close proximity to end-users. These edge servers store cached versions of digital content, ensuring that data doesn't have to traverse long distances, which could lead to increased latency and packet loss.",[842,84862,52316],{},[842,84864,84865],{},"CDNs also ensure high availability and fault tolerance through redundancy. By storing replicated data on multiple servers, CDNs can reroute traffic to alternative servers if one becomes unavailable. This failover mechanism ensures an uninterrupted flow of digital goods to consumers, which is vital for the seamless operation of d-commerce platforms.",[842,84867,52316],{},[842,84869,84870],{},"Moreover, to optimize performance, CDNs leverage load balancing algorithms, evenly distributing network traffic across multiple servers to prevent bottlenecks and avoid server overload. CDNs also employ techniques like TCP connection pre-pooling and route optimization to further enhance content delivery speed.",[842,84872,52316],{},[842,84874,84875],{},"CDNs also make d-commerce more resistant to  Distributed Denial of Service (DDoS) cyberattacks, by absorbing and diffusing traffic surges. Additionally, they implement SSL/TLS encryption to secure the data transmission between the server and the client.",[842,84877,52316],{},[1074,84879,84881],{"id":84880},"payment-gateways-securing-digital-transactions","Payment Gateways: Securing Digital Transactions",[842,84883,84884],{},"Payment gateways function as the cybersecurity bastion safeguarding digital transactions. They bridge the gap between e-commerce platforms and financial institutions, facilitating secure payments while thwarting potential cyber threats.",[842,84886,52316],{},[842,84888,84889],{},"Payment gateways employ rigorous encryption methods to protect sensitive data. During a transaction, credit card information is encrypted using algorithms like AES (Advanced Encryption Standard) or RSA (Rivest-Shamir-Adleman), securing the data during transmission.",[842,84891,52316],{},[842,84893,84894],{},"Moreover, these systems utilize tokenization, where sensitive data is replaced with a non-sensitive equivalent, known as a token. This process further ensures that even if data is intercepted, it's useless without the original decryption key.",[842,84896,52316],{},[842,84898,84899],{},"To enhance the security of online transactions, payment gateways also implement various authentication measures. 3D Secure protocols (such as Verified by Visa or MasterCard SecureCode) involve an additional authentication step, helping to prevent unauthorized transactions.",[842,84901,52316],{},[842,84903,84904],{},"Additionally, payment gateways comply with the Payment Card Industry Data Security Standard (PCI DSS), a set of guidelines designed to ensure secure handling of credit card information. Compliance includes regular audits, network scans, and developing secure systems and applications.",[842,84906,52316],{},[863,84908,84910],{"id":84909},"navigating-the-challenges-real-issues-real-solutions","Navigating the Challenges: Real Issues, Real Solutions",[842,84912,84913],{},"The d-commerce landscape, with its myriad opportunities, is not without challenges. Businesses aiming to thrive must adeptly navigate hurdles related to security, scalability, and compliance. Here, we dissect these challenges and provide some practical solutions to overcome them.",[842,84915,52316],{},[1074,84917,84919],{"id":84918},"scalability-the-ability-to-grow","Scalability: The Ability to Grow",[842,84921,52316],{},[842,84923,84924],{},"In the volatile world of d-commerce, traffic can fluctuate dramatically. Businesses must be prepared to scale their operations to handle sudden surges in traffic and transactions.",[842,84926,52316],{},[842,84928,84929],{},"One strategy is to employ scalable cloud-based infrastructure. Services such as AWS, Google Cloud, and Azure provide flexible scaling options that can be adjusted according to demand. Microservices architecture can also be leveraged for building scalable applications. It allows businesses to deploy and scale individual components independently, providing a granular level of control.",[842,84931,52316],{},[1074,84933,84935],{"id":84934},"compliance-abiding-by-the-rules","Compliance: Abiding by the Rules",[842,84937,52316],{},[842,84939,84940],{},"(Un)fortunately, 90s are no more. These days digital world is governed by various regulations related to data privacy, taxation, and digital rights. Businesses must ensure they adhere to these regulations to avoid penalties and uphold their reputation.",[842,84942,52316],{},[842,84944,84945],{},"Staying compliant begins with understanding the regulations applicable to your business. This may include GDPR for data privacy, PCI DSS for payment security, and regional tax laws. Using compliance management software can help businesses monitor and maintain compliance with these regulations. It's also beneficial to seek legal counsel, ensuring that you're aware of and compliant with the relevant laws and regulations.",[842,84947,52316],{},[1074,84949,84951],{"id":84950},"security-the-constant-watch-guard","Security: The Constant Watch Guard",[842,84953,52316],{},[842,84955,84956],{},"In an era where data is the new oil, ensuring its security is paramount. Breaches can lead to a multitude of issues, ranging from financial losses to reputational damage. It's vital to protect sensitive customer data and transaction details from cyber threats.",[842,84958,52316],{},[842,84960,84961],{},"Applying robust cryptography measures, such as using symmetric encryption and safe key delivery algorithms, is a fundamental step towards securing data. Adopting multi-factor authentication can add an extra layer of security, making it difficult for unauthorized individuals to gain access to sensitive systems. Employing intrusion detection and prevention systems (IDPS) can also be a game-changer, as they can detect and prevent malicious activities in real-time.",[842,84963,52316],{},[863,84965,84967],{"id":84966},"lessons-from-the-field-successes-and-missteps-in-d-commerce","Lessons from the Field: Successes and Missteps in D-Commerce",[842,84969,52316],{},[842,84971,84972],{},"The world of d-commerce provides a fertile ground for learning, filled with stories of triumphs and stumbles. By studying these real-life scenarios, businesses can glean practical insights to guide their d-commerce journey.",[842,84974,52316],{},[1074,84976,84978],{"id":84977},"steam-embracing-community","Steam: Embracing Community",[842,84980,52316],{},[842,84982,84983],{},"Steam, developed by Valve Corporation, has thrived as a leading platform for video game distribution. A key lesson from Steam's success is its community-centric approach. Steam has built a strong community by providing features like user reviews, forums, and modding capabilities.",[842,84985,52316],{},[842,84987,84988],{},"Moreover, Valve has effectively utilized this community feedback to improve and enhance its platform. By listening to its users and making them feel like part of the process, Valve has created a continuous feedback loop, leading to user-centered improvements and thereby increasing platform stickiness.",[842,84990,52316],{},[842,84992,84993],{},"Businesses should consider how they can similarly engage with their customer base to foster loyalty and encourage repeat business.",[842,84995,52316],{},[1074,84997,84999],{"id":84998},"spotify-mastering-personalization","Spotify: Mastering Personalization",[842,85001,52316],{},[842,85003,85004],{},"Spotify's success story teaches us the power of personalization. The music streaming service has made effective use of AI and machine learning to offer personalized recommendations, which has significantly enhanced user engagement.",[842,85006,52316],{},[842,85008,85009],{},"Spotify's 'Discover Weekly' and 'Daily Mix' playlists are perfect exemplars of personalization. By analyzing listening habits, correlating user behavior, and identifying musical patterns, Spotify crafts uniquely tailored playlists for each user. This level of personalization has set Spotify apart in the crowded music streaming market. Additionally, the ability to follow friends, share playlists, and discover new music through social connections further amplifies the personalized experience, fostering a sense of community among users.",[842,85011,52316],{},[842,85013,85014],{},"This lesson underlines the importance of leveraging data and technology to deliver tailored customer experiences, crucial in the competitive d-commerce space.",[842,85016,52316],{},[1074,85018,85020],{"id":85019},"tidal-missteps-in-value-proposition","Tidal: Missteps in Value Proposition",[842,85022,52316],{},[842,85024,85025],{},"On the other end of the spectrum is Tidal, the music streaming service launched by Jay-Z.  Despite the backing of high-profile artists and an ambitious promise of superior audio quality, the platform faced a steep uphill battle in gaining market traction.",[842,85027,52316],{},[842,85029,85030],{},"Tidal's promise of lossless audio quality and exclusive artist content sought to differentiate it from competitors. However, these features came with a significantly higher subscription cost compared to industry mainstays like Spotify or Apple Music. While audio aficionados might appreciate Tidal's high-fidelity music, the mainstream audience found the added value insufficient to justify the premium cost.",[842,85032,52316],{},[842,85034,85035],{},"Moreover, Tidal's emphasis on exclusive content ended up being a double-edged sword. While it might have attracted fans of specific artists initially, it also created a fragmented listening experience for those who wanted to access a broad range of music under one platform.",[842,85037,52316],{},[1074,85039,85041],{"id":85040},"quibi-misreading-market-needs","Quibi: Misreading Market Needs",[842,85043,52316],{},[842,85045,85046],{},"Quibi serves as a stark example of the potential pitfalls in the d-commerce world. The short-form streaming platform, despite a significant initial investment and a star-studded line-up of content, found itself shuttered within just six months of its launch.",[842,85048,52316],{},[842,85050,85051],{},"Quibi's principal mistake lay in misjudging the market dynamics and audience preferences. It banked heavily on the notion that consumers were looking for paid, high-quality, bite-sized content to consume on the go. However, it missed acknowledging the competitive landscape, with already established platforms offering similar content for free or as part of wider subscriptions.",[842,85053,85054],{},"Moreover, Quibi launched in early 2020, coinciding with the COVID-19 pandemic. The target audience, instead of needing on-the-go content, found themselves confined at home, shifting their preference towards longer, more immersive content and watching Netflix instead.",[863,85056,85058],{"id":85057},"the-road-ahead-preparing-for-the-future-of-d-commerce","The Road Ahead: Preparing for the Future of D-Commerce",[842,85060,85061],{},"As we examine the stories of success and failure in d-commerce, it's clear that we're only at the beginning of this journey. The road ahead promises even more transformational changes, fueled by emerging technologies and shifting consumer behavior.",[842,85063,85064],{},"AI-driven personalization is a potent tool already being utilized effectively by platforms like Spotify, and its role will only amplify. The ability to curate tailored user experiences will be a deciding factor in the competitive landscape of d-commerce. Blockchain, too, shows promise, particularly in DRM, offering a secure and transparent way to manage digital rights, opening new avenues for content distribution.",[842,85066,85067],{},"Furthermore, the rise of subscription-based models, seen in successful platforms such as Steam and Spotify, hints at a shift in consumer preference towards continuous, value-added services over one-time purchases.",[842,85069,85070],{},"As the tides of d-commerce continue to rise and evolve, so too does the need for expert partners to navigate its waters. Here, Bravelab enters the narrative. We're a software house committed to helping businesses with robust, flexible, and forward-thinking d-commerce solutions. Our mission is not just to build software, but to understand and solve the unique challenges that your business faces in the dynamic landscape of digital commerce. If this makes sense to you, do not hesitate to contact us about your d-commerce needs.",{"title":728,"searchDepth":729,"depth":729,"links":85072},[85073,85074,85080,85085,85091],{"id":84769,"depth":729,"text":84770},{"id":84790,"depth":729,"text":84791,"children":85075},[85076,85077,85078,85079],{"id":84799,"depth":1112,"text":84800},{"id":84825,"depth":1112,"text":84826},{"id":84849,"depth":1112,"text":84850},{"id":84880,"depth":1112,"text":84881},{"id":84909,"depth":729,"text":84910,"children":85081},[85082,85083,85084],{"id":84918,"depth":1112,"text":84919},{"id":84934,"depth":1112,"text":84935},{"id":84950,"depth":1112,"text":84951},{"id":84966,"depth":729,"text":84967,"children":85086},[85087,85088,85089,85090],{"id":84977,"depth":1112,"text":84978},{"id":84998,"depth":1112,"text":84999},{"id":85019,"depth":1112,"text":85020},{"id":85040,"depth":1112,"text":85041},{"id":85057,"depth":729,"text":85058},"2023-08-01T00:00:00.000Z","What is d-commerce exactly? It's the evolving realm of digital commerce, specifically focused on the trading of digital goods and services.",{"src":85095},"/images/blog/musictechlab_blog_d-commerce-decoded-cutting-through-the-hype.webp",{"enabled":738,"items":85097},[85098,85100,85102,85104],{"text":85099,"icon":1067},"D-commerce focuses on digital goods: eBooks, streaming, software licenses, and in-game assets.",{"text":85101,"icon":1769},"CDNs, DRM, e-commerce platforms, and payment gateways power the d-commerce stack.",{"text":85103,"icon":7498},"DRM uses encryption to ensure digital content is accessible only to authorized users.",{"text":85105,"icon":11617},"The provider-consumer relationship in d-commerce is ongoing, not a one-time transaction.",{},{"title":418,"description":85093},[52276],"rSyAL08wf9ZyroKyO1WiFw3apVdq88a8Q2a697tfH_M",{"id":85111,"title":34,"authors":723,"badge":85112,"body":85113,"category":4990,"client":85361,"date":85362,"description":85363,"extension":734,"faq":723,"featured":738,"featuredOrder":3473,"hidden":69,"image":85364,"keyTakeaways":85366,"meta":85376,"navigation":738,"path":35,"seo":85377,"status":723,"stem":36,"tags":85378,"teaser":723,"__hash__":85379},"posts/blog/case-study/mobile-app-for-music-catalog.md",{"label":5,"color":50099},{"type":725,"value":85114,"toc":85351},[85115,85118,85120,85122,85139,85141,85143,85171,85173,85177,85184,85203,85205,85207,85256,85258,85262,85269,85284,85293,85295,85297,85302,85306,85311,85313,85316,85335,85337,85339],[842,85116,85117],{},"Content creators, music publishers, and producers need their catalogs where their audience is — on mobile. Our white-label mobile app for music catalogs was built to solve exactly that: a ready-to-deploy Flutter application that brings music libraries to iOS and Android with a professional audio player, playlists, search, downloads, and more.",[4937,85119],{},[863,85121,67026],{"id":67025},[1045,85123,85125,85129,85134],{"className":85124},[1048,1049,1050,1051,1052],[1054,85126],{"description":85127,"title":85128,"icon":4855},"Customers live on Spotify, Apple Music, and Tidal — but many music businesses are still desktop-first, missing mobile-native users entirely.","Clients Are Mobile",[1054,85130],{"description":85131,"title":85132,"icon":85133},"Revenues from big platforms keep shrinking. Diversifying through direct-to-fan channels and cultivating super-fans is no longer optional.","Shrinking Platform Revenue","i-lucide-trending-down",[1054,85135],{"description":85136,"title":85137,"icon":85138},"While other industries embraced digital tools, many music businesses still deal with paper contracts, email chains, and outdated processes.","Analog Practices","i-lucide-file-stack",[4937,85140],{},[863,85142,34800],{"id":33369},[1045,85144,85146,85151,85156,85160,85164,85167],{"className":85145},[1048,1049,1050,1051,1052],[1054,85147],{"description":85148,"title":85149,"icon":85150},"Built-in media player using iOS/Android native audio services. Background playback, lock screen controls, waveform visualisation, and stem switching.","Audio Player","i-lucide-play-circle",[1054,85152],{"description":85153,"title":85154,"icon":85155},"Staff-curated and user-created playlists. Organise, customise, and share collections of tracks.","Playlists","i-lucide-list-music",[1054,85157],{"description":85158,"title":85159,"icon":7560},"Filter by genre, artist, album, duration, mood, theme, BPM, and custom tags. Sort results and apply multiple filters simultaneously.","Advanced Search",[1054,85161],{"description":85162,"title":85163,"icon":5512},"Mark and quickly access preferred songs, albums, or artists. Synced across devices via the backend API.","Favourites",[1054,85165],{"description":85166,"title":72126,"icon":84498},"Save music locally for offline listening. Background download manager with progress tracking and storage management.",[1054,85168],{"description":85169,"title":43335,"icon":85170},"Share tracks and playlists with friends via native share sheets. Deep linking support for direct track access.","i-lucide-share-2",[4937,85172],{},[863,85174,85176],{"id":85175},"architecture","Architecture",[842,85178,85179,85180,85183],{},"The app follows a ",[996,85181,85182],{},"feature-first architecture"," with clean separation of concerns. Each feature module contains its own UI, BLoC/Cubit state management, repository layer, and models.",[1045,85185,85187,85191,85195,85199],{"className":85186},[1048,1049,1765,1051,1052],[1054,85188],{"description":85189,"title":85190,"icon":1769},"Audio Player, Auth (login, sign-up, reset password, social auth), Browse & Search, Favourites, Playlists, Account Management, Downloads, Filters (genre, mood, theme, BPM).","Feature Modules",[1054,85192],{"description":85193,"title":85194,"icon":9547},"Native audio service integration with `just_audio` and `audio_service`. Background playback, waveform rendering with `flutter_audio_waveforms`, volume control, and stem switching for multi-track playback.","Audio Engine",[1054,85196],{"description":85197,"title":85198,"icon":2895},"Hasura GraphQL API with `hasura_connect`. Sanity CMS for content. Secure token storage with `flutter_secure_storage`. Offline caching with `background_downloader`.","Data Layer",[1054,85200],{"description":85201,"title":85202,"icon":1057},"Email/password, Google Sign-In, Apple Sign-In. Email verification flow. Password reset with validation. Secure storage for tokens and credentials.","Auth & Security",[4937,85204],{},[863,85206,65386],{"id":65385},[1045,85208,85210,85213,85217,85221,85224,85228,85232,85236,85239,85241,85246,85251],{"className":85209},[1048,50574,1050,50642,50239,1052],[1054,85211],{"description":85212,"title":47091,"icon":4855},"Cross-platform app",[1054,85214],{"description":85215,"title":85216,"icon":8737},"State management","BLoC / Cubit",[1054,85218],{"description":85219,"title":85220,"icon":52124},"API layer","Hasura + GraphQL",[1054,85222],{"description":52128,"title":85223,"icon":3850},"Sanity CMS",[1054,85225],{"description":85226,"title":85227,"icon":9547},"Audio playback","just_audio",[1054,85229],{"description":85230,"title":85231,"icon":3649},"Background audio","audio_service",[1054,85233],{"description":85234,"title":85235,"icon":7498},"Social auth","Sign-In (Google/Apple)",[1054,85237],{"description":85238,"title":72309,"icon":66072},"CI/CD builds",[1054,85240],{"description":65815,"title":65816,"icon":64001},[1054,85242],{"description":85243,"title":85244,"icon":85245},"i18n support","easy_localization","i-lucide-languages",[1054,85247],{"description":85248,"title":85249,"icon":85250},"Navigation","auto_route","i-lucide-route",[1054,85252],{"description":85253,"title":85254,"icon":85255},"Immutable models","Freezed","i-lucide-snowflake",[4937,85257],{},[863,85259,85261],{"id":85260},"white-label-approach","White-Label Approach",[842,85263,85264,85265,85268],{},"The app is designed as a ",[996,85266,85267],{},"white-label product"," — fully brandable and customisable for different music businesses.",[1045,85270,85272,85276,85280],{"className":85271},[1048,1049,1050,1051,1052],[1054,85273],{"description":85274,"title":85275,"icon":5507},"Custom logos, colours, typography, and visual identity. The app looks and feels like your own product.","Your Brand",[1054,85277],{"description":85278,"title":85279,"icon":13562},"External config file for API keys, environment settings, and feature flags. No code changes needed for basic customisation.","Configurable",[1054,85281],{"description":85282,"title":85283,"icon":37696},"Expect your branded app published on Google Play and App Store within approximately 4 weeks.","Fast to Market",[1572,85285,85286],{},[842,85287,85288,85289,85292],{},"The basic white-label package starts at ",[996,85290,85291],{},"€8,700"," — including the app, marketplace submission for iOS and Android, and initial setup support. Custom features and integrations are scoped separately.",[4937,85294],{},[863,85296,66034],{"id":66033},[41054,85298,85299],{},[842,85300,85301],{},"Our goal was to build a music platform enabling us to charge our customers in a subscription model and as one-off payments. MusicTech Lab's developers were super engaged. Thanks to their effort, we prepared MVP and started working on the marketing. The first biggest technology milestone was achieved during five months.",[842,85303,85304,72331],{},[996,85305,72330],{},[842,85307,85308],{},[846,85309,72338],{"href":72336,"rel":85310},[850],[4937,85312],{},[863,85314,72132],{"id":85315},"faq",[1045,85317,85319,85323,85327,85331],{"className":85318},[1048,1049,1765,1051,1052],[1054,85320],{"description":85321,"title":85322},"More than half the world (54%) uses mobile devices, surpassing desktop. Most music consumption happens on mobile — Spotify, YouTube Music, Tidal, Apple Music. Reach your audience where they already are.","Why should I move my business to mobile?",[1054,85324],{"description":85325,"title":85326},"Within approximately 4 weeks. The timeline may vary based on customisations. Your app will be fully white-labelled with your logos, colours, and brand elements.","When can I expect my app published?",[1054,85328],{"description":85329,"title":85330},"Yes. We start with the core features and customise to your requirements. No need to include every \"nice-to-have\" upfront — we add features iteratively as needed.","Can I adjust the app to my needs?",[1054,85332],{"description":85333,"title":85334},"The basic package starts at €8,700 — including the app, marketplace submission, and initial support. Custom features, integrations, and ongoing maintenance are scoped separately.","What does the investment look like?",[4937,85336],{},[863,85338,51026],{"id":51025},[1045,85340,85342,85344,85346,85348],{"className":85341},[13033,50238,50239,1052],[50241,85343],{"color":50243,"label":72417,"to":72418,"variant":50246,"target":50245},[50241,85345],{"color":50243,"label":49659,"to":72421,"variant":50246,"target":50245},[50241,85347],{"color":50249,"label":72424,"to":72425,"variant":50246,"target":50245},[50241,85349],{"color":50249,"label":85350,"to":19,"variant":50246},"Full Platform Case Study",{"title":728,"searchDepth":729,"depth":729,"links":85352},[85353,85354,85355,85356,85357,85358,85359,85360],{"id":67025,"depth":729,"text":67026},{"id":33369,"depth":729,"text":34800},{"id":85175,"depth":729,"text":85176},{"id":65385,"depth":729,"text":65386},{"id":85260,"depth":729,"text":85261},{"id":66033,"depth":729,"text":66034},{"id":85315,"depth":729,"text":72132},{"id":51025,"depth":729,"text":51026},{"name":72445,"logo":72446},"2023-06-01T00:00:00.000Z","A white-label Flutter mobile app for music catalogs — built for publishers, producers, and music lovers. Audio player, playlists, search, downloads, and offline mode.",{"src":85365},"/images/case-studies/musictechlab_mobile-app-for-music-catalog.webp",{"enabled":738,"items":85367},[85368,85370,85372,85374],{"text":85369,"icon":4855},"White-label Flutter music app ready for App Store and Google Play in 4 weeks.",{"text":85371,"icon":1774},"Basic package starts at 8,700 EUR including app, store submission, and setup support.",{"text":85373,"icon":40852},"Features include offline downloads, background playback, and advanced search filters.",{"text":85375,"icon":5365},"Cross-platform codebase with BLoC state management and Hasura GraphQL API.",{},{"title":34,"description":85363},[4990,5523,765],"1KZGlKLGCro32dTNOJZFDqpMbdm5t9AkyAlT2rlK7LM",{"id":85381,"title":430,"authors":85382,"badge":723,"body":85385,"category":756,"client":723,"date":85468,"description":85469,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":85470,"keyTakeaways":85472,"meta":85483,"navigation":738,"path":431,"seo":85484,"status":723,"stem":432,"tags":85485,"teaser":723,"__hash__":85486},"posts/blog/software-development/dev-meeting-004-introduction-to-event-storming.md",[85383],{"name":834,"to":720,"avatar":85384},{"src":722},{"type":725,"value":85386,"toc":85463},[85387,85396,85405,85409,85428,85432,85441,85445,85448],[842,85388,85389,85390,85395],{},"Event Storming is a workshop-based method for exploring complex systems and business domains. Using sticky notes (or digital equivalents), participants map out events, actions, and actors to build a shared understanding of how a system works. The method was created by ",[846,85391,85394],{"href":85392,"rel":85393},"https://www.linkedin.com/in/brando/",[850],"Alberto Brandolini"," and is widely used in Domain-Driven Design (DDD) contexts.",[842,85397,85398,85399,85404],{},"For this dev meeting, we invited ",[846,85400,85403],{"href":85401,"rel":85402},"https://www.linkedin.com/in/mariuszgil/",[850],"Mariusz Gil"," — architect, ex-CTO, trainer, and conference speaker focused on high-performance and scalable web applications.",[863,85406,85408],{"id":85407},"key-rules","Key Rules",[1045,85410,85412,85416,85420,85424],{"className":85411},[1048,1049,1765,1051,1052],[1054,85413],{"description":85414,"title":85415},"Have a clear understanding of the problem you're solving before the workshop begins. This keeps the team focused.","Start with the business goal",[1054,85417],{"description":85418,"title":85419},"The visual nature of Event Storming is essential. Sticky notes (or digital tools like Miro) represent events, commands, and actors.","Use sticky notes",[1054,85421],{"description":85422,"title":85423},"Include people from business, development, and operations. Different viewpoints surface issues that would otherwise be missed.","Involve multiple perspectives",[1054,85425],{"description":85426,"title":85427},"This isn't about detailed technical diagrams. It's a high-level view of how events flow through the system.","Keep it simple",[863,85429,85431],{"id":85430},"pros-and-cons","Pros and Cons",[1045,85433,85435,85438],{"className":85434},[1048,1049,1765,1051,1052],[1054,85436],{"description":85437,"title":13686},"- Creates a shared understanding across teams - Identifies bottlenecks and improvement opportunities - Encourages collaboration and communication - Works across different domains (business processes, software, org structures) - Can be done remotely with digital tools - The output serves as living documentation",[1054,85439],{"description":85440,"title":13691},"- Time-consuming for complex systems - Requires a skilled facilitator - Best suited for event-driven systems — less effective for simple interactions - Captures a single point in time — may miss evolving aspects of the system - Needs a collaborative team culture to be effective",[863,85442,85444],{"id":85443},"when-to-use-it","When to Use It",[842,85446,85447],{},"Event Storming works best when you're starting a new project and need to understand a complex domain, when you're refactoring a legacy system and want to map existing behaviour, or when business and development teams need to align on how a process actually works (not how documentation says it works).",[1572,85449,85450],{},[842,85451,85452,85453,85458,85459,861],{},"For a deeper dive, check out Alberto Brandolini's book ",[846,85454,85457],{"href":85455,"rel":85456},"https://www.eventstorming.com/book/",[850],"Introducing EventStorming"," and Mariusz Gil's ",[846,85460,85462],{"href":85401,"rel":85461},[850],"talks and workshops",{"title":728,"searchDepth":729,"depth":729,"links":85464},[85465,85466,85467],{"id":85407,"depth":729,"text":85408},{"id":85430,"depth":729,"text":85431},{"id":85443,"depth":729,"text":85444},"2023-01-26T00:00:00.000Z","Event Storming is a workshop-style method for exploring complex systems and business domains by mapping the flow of events and interactions.",{"src":85471},"/images/blog/musictechlab_blog_dev-meeting-004-introduction-to-event-storming.webp",{"enabled":738,"items":85473},[85474,85477,85479,85481],{"text":85475,"icon":85476},"Event Storming maps business events with sticky notes to build shared understanding.","i-lucide-layout",{"text":85478,"icon":4845},"Include business, development, and operations perspectives to surface hidden issues.",{"text":85480,"icon":8737},"Best for new projects, legacy refactoring, or aligning teams on actual processes.",{"text":85482,"icon":50270},"Requires a skilled facilitator and a collaborative team culture to be effective.",{},{"title":430,"description":85469},[18784],"QknuxetZ-PdfGun2Bs061_BNpqRu82Ro5S4sC8Qxg68",{"id":85488,"title":426,"authors":85489,"badge":723,"body":85492,"category":756,"client":723,"date":85560,"description":85561,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":85562,"keyTakeaways":85564,"meta":85572,"navigation":738,"path":427,"seo":85573,"status":723,"stem":428,"tags":85574,"teaser":723,"__hash__":85575},"posts/blog/software-development/dev-meeting-003-web3-primer.md",[85490],{"name":834,"to":720,"avatar":85491},{"src":722},{"type":725,"value":85493,"toc":85556},[85494,85503,85513,85517,85532,85536,85539],[842,85495,85496,85497,85502],{},"At MusicTech Lab, we keep an eye on how technology reshapes creative industries. In early 2023, Web3 was one of the biggest conversations in tech. We invited ",[846,85498,85501],{"href":85499,"rel":85500},"https://bit.ly/3XskJ0C",[850],"Maciej Nowak"," — marketer and Web3 podcast host — to walk us through the fundamentals.",[1572,85504,85505],{},[842,85506,85507,85508,861],{},"This post is a summary of an internal dev meeting. For Maciej's full takes on Web3, check out his ",[846,85509,85512],{"href":85510,"rel":85511},"https://bit.ly/3QJblmY",[850],"podcast",[863,85514,85516],{"id":85515},"the-evolution-of-the-web","The Evolution of the Web",[1045,85518,85520,85524,85528],{"className":85519},[1048,1049,1050,1051,1052],[1054,85521],{"description":85522,"title":85523},"Static company websites. Content served to the user. One-way information flow.","Web1 — READ",[1054,85525],{"description":85526,"title":85527},"Social media, SaaS platforms. Users actively co-create content — but platforms own the data.","Web2 — WRITE",[1054,85529],{"description":85530,"title":85531},"Value exchange without intermediaries. Creators retain rights to their content. New forms of applications and digital ownership.","Web3 — OWN",[863,85533,85535],{"id":85534},"about-maciej-nowak","About Maciej Nowak",[842,85537,85538],{},"Maciej helps brands build marketing strategies through online channels. He co-hosts a podcast dedicated to Web3 topics.",[1045,85540,85542,85544,85548,85552],{"className":85541},[13033,50238,50239,1052],[50241,85543],{"color":50243,"label":5669,"target":50245,"to":85499,"variant":50246},[50241,85545],{"color":50249,"label":85546,"target":50245,"to":85547,"variant":50246},"Osom Studio","https://www.osomstudio.com/",[50241,85549],{"color":50249,"label":85550,"target":50245,"to":85551,"variant":50246},"Osom to Know","https://www.youtube.com/@osomtoknow",[50241,85553],{"color":50249,"label":85554,"target":50245,"to":85555,"variant":50246},"PPW3 Podcast","https://www.youtube.com/@ppw3",{"title":728,"searchDepth":729,"depth":729,"links":85557},[85558,85559],{"id":85515,"depth":729,"text":85516},{"id":85534,"depth":729,"text":85535},"2023-01-19T00:00:00.000Z","A primer on Web3 fundamentals. Learn how network interactions are evolving and why companies are adopting Web3 elements in their projects.",{"src":85563},"/images/blog/musictechlab_blog_dev-meeting-ddd.webp",{"enabled":738,"items":85565},[85566,85568,85570],{"text":85567,"icon":1067},"Web1 = read, Web2 = write, Web3 = own. Each era redefines user interaction.",{"text":85569,"icon":7498},"Web3 enables value exchange without intermediaries and creator-owned content.",{"text":85571,"icon":12409},"Companies are already adopting Web3 elements like digital ownership into products.",{},{"title":426,"description":85561},[18784],"tEWTYmmppbO5RKpKnvvdwQO_jOWHsiahOr-l5bwNKVo",{"id":85577,"title":422,"authors":85578,"badge":723,"body":85581,"category":756,"client":723,"date":86354,"description":86355,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":86356,"keyTakeaways":86358,"meta":86368,"navigation":738,"path":423,"seo":86369,"status":723,"stem":424,"tags":86370,"teaser":723,"__hash__":86371},"posts/blog/software-development/dev-meeting-002-introduction-to-domain-driven-design-ddd.md",[85579],{"name":834,"to":720,"avatar":85580},{"src":722},{"type":725,"value":85582,"toc":86347},[85583,85590,85599,85603,85622,85626,85629,86258,86276,86280,86299,86303,86306,86309,86323,86327,86333,86344],[842,85584,85585,85586,85589],{},"Domain-Driven Design (DDD) is an approach to software development where the code is structured around the ",[996,85587,85588],{},"business domain"," rather than technical layers. Instead of starting with the database or framework, you start with the problem the business is trying to solve — and let that shape the architecture.",[842,85591,85592,85593,85598],{},"For this dev meeting, ",[846,85594,85597],{"href":85595,"rel":85596},"https://www.linkedin.com/in/%F0%9F%A7%99-mateusz-kubaszek-58306466/",[850],"Mateusz Kubaszek"," — consultant, software architect, and data engineering architect — walked us through the core concepts with Python examples.",[863,85600,85602],{"id":85601},"core-concepts","Core Concepts",[1045,85604,85606,85610,85614,85618],{"className":85605},[1048,1049,1765,1051,1052],[1054,85607],{"description":85608,"title":85609},"A shared vocabulary between developers and domain experts. If the business says \"Release,\" the code has a `Release` class — not a `ContentItem` or `Product`. This eliminates translation errors.","Ubiquitous Language",[1054,85611],{"description":85612,"title":85613},"Large systems are split into distinct areas, each with its own model. A \"Track\" in the catalog context is different from a \"Track\" in the royalty context — and that's intentional.","Bounded Contexts",[1054,85615],{"description":85616,"title":85617},"Entities have identity (a specific artist, a specific release). Value Objects don't — they're defined by their attributes (an ISRC code, a money amount). This distinction drives how you model data.","Entities & Value Objects",[1054,85619],{"description":85620,"title":85621},"A cluster of entities treated as a single unit for data changes. For example, a `Release` aggregate might include its `Tracks`, `Artwork`, and `DealTerms` — all modified together.","Aggregates",[863,85623,85625],{"id":85624},"a-music-industry-example","A Music Industry Example",[842,85627,85628],{},"Here's what DDD looks like applied to a music distribution platform:",[1013,85630,85632],{"className":1368,"code":85631,"language":1250,"meta":728,"style":728},"# Value Object — defined by its attributes, no identity\nclass ISRC:\n    def __init__(self, code: str):\n        if not self._validate(code):\n            raise ValueError(f\"Invalid ISRC: {code}\")\n        self.code = code\n\n    @staticmethod\n    def _validate(code: str) -> bool:\n        return len(code) == 12 and code[:2].isalpha()\n\n    def __eq__(self, other):\n        return isinstance(other, ISRC) and self.code == other.code\n\n\n# Entity — has identity (id), mutable state\nclass Track:\n    def __init__(self, track_id: str, title: str, isrc: ISRC, duration_seconds: int):\n        self.track_id = track_id\n        self.title = title\n        self.isrc = isrc\n        self.duration_seconds = duration_seconds\n\n\n# Aggregate Root — controls access to child entities\nclass Release:\n    def __init__(self, release_id: str, title: str, artist: str):\n        self.release_id = release_id\n        self.title = title\n        self.artist = artist\n        self._tracks: list[Track] = []\n\n    def add_track(self, track: Track):\n        if any(t.isrc == track.isrc for t in self._tracks):\n            raise ValueError(f\"Track with ISRC {track.isrc.code} already exists\")\n        self._tracks.append(track)\n\n    @property\n    def total_duration(self) -> int:\n        return sum(t.duration_seconds for t in self._tracks)\n",[895,85633,85634,85639,85648,85669,85688,85712,85725,85729,85735,85758,85791,85795,85813,85848,85852,85856,85861,85870,85916,85929,85942,85955,85968,85972,85976,85981,85990,86026,86039,86051,86064,86087,86091,86112,86150,86182,86200,86204,86211,86230],{"__ignoreMap":728},[1086,85635,85636],{"class":1088,"line":1089},[1086,85637,85638],{"class":1427},"# Value Object — defined by its attributes, no identity\n",[1086,85640,85641,85643,85646],{"class":1088,"line":729},[1086,85642,4036],{"class":1155},[1086,85644,85645],{"class":1092}," ISRC",[1086,85647,1418],{"class":1146},[1086,85649,85650,85652,85654,85656,85658,85660,85663,85665,85667],{"class":1088,"line":1112},[1086,85651,4065],{"class":1155},[1086,85653,83116],{"class":1105},[1086,85655,1398],{"class":1146},[1086,85657,4074],{"class":4073},[1086,85659,1227],{"class":1146},[1086,85661,85662],{"class":1401}," code",[1086,85664,1133],{"class":1146},[1086,85666,1407],{"class":1092},[1086,85668,4047],{"class":1146},[1086,85670,85671,85673,85675,85677,85679,85682,85684,85686],{"class":1088,"line":1181},[1086,85672,6800],{"class":1423},[1086,85674,6803],{"class":1146},[1086,85676,23768],{"class":1436},[1086,85678,861],{"class":1146},[1086,85680,85681],{"class":1105},"_validate",[1086,85683,1398],{"class":1146},[1086,85685,895],{"class":1105},[1086,85687,4047],{"class":1146},[1086,85689,85690,85692,85695,85697,85699,85702,85704,85706,85708,85710],{"class":1088,"line":1205},[1086,85691,6831],{"class":1423},[1086,85693,85694],{"class":1092}," ValueError",[1086,85696,1398],{"class":1146},[1086,85698,5962],{"class":1155},[1086,85700,85701],{"class":1096},"\"Invalid ISRC: ",[1086,85703,4409],{"class":1187},[1086,85705,895],{"class":1105},[1086,85707,4423],{"class":1187},[1086,85709,1159],{"class":1096},[1086,85711,1455],{"class":1146},[1086,85713,85714,85716,85718,85720,85722],{"class":1088,"line":1276},[1086,85715,23875],{"class":1436},[1086,85717,861],{"class":1146},[1086,85719,895],{"class":4109},[1086,85721,19552],{"class":1146},[1086,85723,85724],{"class":1436}," code\n",[1086,85726,85727],{"class":1088,"line":1282},[1086,85728,3390],{"emptyLinePlaceholder":738},[1086,85730,85731,85733],{"class":1088,"line":1288},[1086,85732,6734],{"class":1146},[1086,85734,6737],{"class":1092},[1086,85736,85737,85739,85742,85744,85746,85748,85750,85752,85754,85756],{"class":1088,"line":2685},[1086,85738,4065],{"class":1155},[1086,85740,85741],{"class":1105}," _validate",[1086,85743,1398],{"class":1146},[1086,85745,895],{"class":1401},[1086,85747,1133],{"class":1146},[1086,85749,1407],{"class":1092},[1086,85751,1410],{"class":1146},[1086,85753,1413],{"class":1146},[1086,85755,19501],{"class":1092},[1086,85757,1418],{"class":1146},[1086,85759,85760,85762,85764,85766,85768,85770,85772,85775,85778,85780,85782,85784,85786,85789],{"class":1088,"line":2700},[1086,85761,4239],{"class":1423},[1086,85763,30717],{"class":1105},[1086,85765,1398],{"class":1146},[1086,85767,895],{"class":1105},[1086,85769,1410],{"class":1146},[1086,85771,10847],{"class":1146},[1086,85773,85774],{"class":1187}," 12",[1086,85776,85777],{"class":1146}," and",[1086,85779,85662],{"class":1436},[1086,85781,29247],{"class":1146},[1086,85783,1483],{"class":1187},[1086,85785,29480],{"class":1146},[1086,85787,85788],{"class":1105},"isalpha",[1086,85790,1387],{"class":1146},[1086,85792,85793],{"class":1088,"line":3398},[1086,85794,3390],{"emptyLinePlaceholder":738},[1086,85796,85797,85799,85802,85804,85806,85808,85811],{"class":1088,"line":1715},[1086,85798,4065],{"class":1155},[1086,85800,85801],{"class":1105}," __eq__",[1086,85803,1398],{"class":1146},[1086,85805,4074],{"class":4073},[1086,85807,1227],{"class":1146},[1086,85809,85810],{"class":1401}," other",[1086,85812,4047],{"class":1146},[1086,85814,85815,85817,85820,85822,85825,85827,85829,85831,85833,85835,85837,85839,85841,85843,85845],{"class":1088,"line":3409},[1086,85816,4239],{"class":1423},[1086,85818,85819],{"class":1105}," isinstance",[1086,85821,1398],{"class":1146},[1086,85823,85824],{"class":1105},"other",[1086,85826,1227],{"class":1146},[1086,85828,85645],{"class":1105},[1086,85830,1410],{"class":1146},[1086,85832,85777],{"class":1146},[1086,85834,23768],{"class":1436},[1086,85836,861],{"class":1146},[1086,85838,895],{"class":4109},[1086,85840,10847],{"class":1146},[1086,85842,85810],{"class":1436},[1086,85844,861],{"class":1146},[1086,85846,85847],{"class":4109},"code\n",[1086,85849,85850],{"class":1088,"line":3415},[1086,85851,3390],{"emptyLinePlaceholder":738},[1086,85853,85854],{"class":1088,"line":3421},[1086,85855,3390],{"emptyLinePlaceholder":738},[1086,85857,85858],{"class":1088,"line":3427},[1086,85859,85860],{"class":1427},"# Entity — has identity (id), mutable state\n",[1086,85862,85863,85865,85868],{"class":1088,"line":3433},[1086,85864,4036],{"class":1155},[1086,85866,85867],{"class":1092}," Track",[1086,85869,1418],{"class":1146},[1086,85871,85872,85874,85876,85878,85880,85882,85884,85886,85888,85890,85893,85895,85897,85899,85901,85903,85905,85907,85910,85912,85914],{"class":1088,"line":3439},[1086,85873,4065],{"class":1155},[1086,85875,83116],{"class":1105},[1086,85877,1398],{"class":1146},[1086,85879,4074],{"class":4073},[1086,85881,1227],{"class":1146},[1086,85883,60637],{"class":1401},[1086,85885,1133],{"class":1146},[1086,85887,1407],{"class":1092},[1086,85889,1227],{"class":1146},[1086,85891,85892],{"class":1401}," title",[1086,85894,1133],{"class":1146},[1086,85896,1407],{"class":1092},[1086,85898,1227],{"class":1146},[1086,85900,1564],{"class":1401},[1086,85902,1133],{"class":1146},[1086,85904,85645],{"class":1436},[1086,85906,1227],{"class":1146},[1086,85908,85909],{"class":1401}," duration_seconds",[1086,85911,1133],{"class":1146},[1086,85913,22856],{"class":1092},[1086,85915,4047],{"class":1146},[1086,85917,85918,85920,85922,85924,85926],{"class":1088,"line":3444},[1086,85919,23875],{"class":1436},[1086,85921,861],{"class":1146},[1086,85923,32479],{"class":4109},[1086,85925,19552],{"class":1146},[1086,85927,85928],{"class":1436}," track_id\n",[1086,85930,85931,85933,85935,85937,85939],{"class":1088,"line":3450},[1086,85932,23875],{"class":1436},[1086,85934,861],{"class":1146},[1086,85936,9069],{"class":4109},[1086,85938,19552],{"class":1146},[1086,85940,85941],{"class":1436}," title\n",[1086,85943,85944,85946,85948,85950,85952],{"class":1088,"line":3456},[1086,85945,23875],{"class":1436},[1086,85947,861],{"class":1146},[1086,85949,1402],{"class":4109},[1086,85951,19552],{"class":1146},[1086,85953,85954],{"class":1436}," isrc\n",[1086,85956,85957,85959,85961,85963,85965],{"class":1088,"line":3462},[1086,85958,23875],{"class":1436},[1086,85960,861],{"class":1146},[1086,85962,8993],{"class":4109},[1086,85964,19552],{"class":1146},[1086,85966,85967],{"class":1436}," duration_seconds\n",[1086,85969,85970],{"class":1088,"line":3467},[1086,85971,3390],{"emptyLinePlaceholder":738},[1086,85973,85974],{"class":1088,"line":3473},[1086,85975,3390],{"emptyLinePlaceholder":738},[1086,85977,85978],{"class":1088,"line":3479},[1086,85979,85980],{"class":1427},"# Aggregate Root — controls access to child entities\n",[1086,85982,85983,85985,85988],{"class":1088,"line":3485},[1086,85984,4036],{"class":1155},[1086,85986,85987],{"class":1092}," Release",[1086,85989,1418],{"class":1146},[1086,85991,85992,85994,85996,85998,86000,86002,86004,86006,86008,86010,86012,86014,86016,86018,86020,86022,86024],{"class":1088,"line":3491},[1086,85993,4065],{"class":1155},[1086,85995,83116],{"class":1105},[1086,85997,1398],{"class":1146},[1086,85999,4074],{"class":4073},[1086,86001,1227],{"class":1146},[1086,86003,17744],{"class":1401},[1086,86005,1133],{"class":1146},[1086,86007,1407],{"class":1092},[1086,86009,1227],{"class":1146},[1086,86011,85892],{"class":1401},[1086,86013,1133],{"class":1146},[1086,86015,1407],{"class":1092},[1086,86017,1227],{"class":1146},[1086,86019,81651],{"class":1401},[1086,86021,1133],{"class":1146},[1086,86023,1407],{"class":1092},[1086,86025,4047],{"class":1146},[1086,86027,86028,86030,86032,86034,86036],{"class":1088,"line":3497},[1086,86029,23875],{"class":1436},[1086,86031,861],{"class":1146},[1086,86033,17638],{"class":4109},[1086,86035,19552],{"class":1146},[1086,86037,86038],{"class":1436}," release_id\n",[1086,86040,86041,86043,86045,86047,86049],{"class":1088,"line":3503},[1086,86042,23875],{"class":1436},[1086,86044,861],{"class":1146},[1086,86046,9069],{"class":4109},[1086,86048,19552],{"class":1146},[1086,86050,85941],{"class":1436},[1086,86052,86053,86055,86057,86059,86061],{"class":1088,"line":3509},[1086,86054,23875],{"class":1436},[1086,86056,861],{"class":1146},[1086,86058,7377],{"class":4109},[1086,86060,19552],{"class":1146},[1086,86062,86063],{"class":1436}," artist\n",[1086,86065,86066,86068,86070,86073,86075,86077,86079,86081,86083,86085],{"class":1088,"line":3515},[1086,86067,23875],{"class":1436},[1086,86069,861],{"class":1146},[1086,86071,86072],{"class":4109},"_tracks",[1086,86074,1133],{"class":1146},[1086,86076,20579],{"class":1436},[1086,86078,4340],{"class":1146},[1086,86080,68442],{"class":1436},[1086,86082,4420],{"class":1146},[1086,86084,19552],{"class":1146},[1086,86086,5920],{"class":1146},[1086,86088,86089],{"class":1088,"line":3520},[1086,86090,3390],{"emptyLinePlaceholder":738},[1086,86092,86093,86095,86098,86100,86102,86104,86106,86108,86110],{"class":1088,"line":3526},[1086,86094,4065],{"class":1155},[1086,86096,86097],{"class":1105}," add_track",[1086,86099,1398],{"class":1146},[1086,86101,4074],{"class":4073},[1086,86103,1227],{"class":1146},[1086,86105,60461],{"class":1401},[1086,86107,1133],{"class":1146},[1086,86109,85867],{"class":1436},[1086,86111,4047],{"class":1146},[1086,86113,86114,86116,86119,86121,86123,86125,86127,86129,86131,86133,86135,86138,86140,86142,86144,86146,86148],{"class":1088,"line":3531},[1086,86115,6800],{"class":1423},[1086,86117,86118],{"class":1105}," any",[1086,86120,1398],{"class":1146},[1086,86122,62279],{"class":1105},[1086,86124,861],{"class":1146},[1086,86126,1402],{"class":4109},[1086,86128,10847],{"class":1146},[1086,86130,60461],{"class":1105},[1086,86132,861],{"class":1146},[1086,86134,1402],{"class":4109},[1086,86136,86137],{"class":1423}," for",[1086,86139,47636],{"class":1105},[1086,86141,5931],{"class":1423},[1086,86143,23768],{"class":1436},[1086,86145,861],{"class":1146},[1086,86147,86072],{"class":4109},[1086,86149,4047],{"class":1146},[1086,86151,86152,86154,86156,86158,86160,86163,86165,86167,86169,86171,86173,86175,86177,86180],{"class":1088,"line":3537},[1086,86153,6831],{"class":1423},[1086,86155,85694],{"class":1092},[1086,86157,1398],{"class":1146},[1086,86159,5962],{"class":1155},[1086,86161,86162],{"class":1096},"\"Track with ISRC ",[1086,86164,4409],{"class":1187},[1086,86166,33099],{"class":1105},[1086,86168,861],{"class":1146},[1086,86170,1402],{"class":4109},[1086,86172,861],{"class":1146},[1086,86174,895],{"class":4109},[1086,86176,4423],{"class":1187},[1086,86178,86179],{"class":1096}," already exists\"",[1086,86181,1455],{"class":1146},[1086,86183,86184,86186,86188,86190,86192,86194,86196,86198],{"class":1088,"line":3543},[1086,86185,23875],{"class":1436},[1086,86187,861],{"class":1146},[1086,86189,86072],{"class":4109},[1086,86191,861],{"class":1146},[1086,86193,5957],{"class":1105},[1086,86195,1398],{"class":1146},[1086,86197,33099],{"class":1105},[1086,86199,1455],{"class":1146},[1086,86201,86202],{"class":1088,"line":3549},[1086,86203,3390],{"emptyLinePlaceholder":738},[1086,86205,86206,86208],{"class":1088,"line":3555},[1086,86207,6734],{"class":1146},[1086,86209,86210],{"class":1092},"property\n",[1086,86212,86213,86215,86218,86220,86222,86224,86226,86228],{"class":1088,"line":3561},[1086,86214,4065],{"class":1155},[1086,86216,86217],{"class":1105}," total_duration",[1086,86219,1398],{"class":1146},[1086,86221,4074],{"class":4073},[1086,86223,1410],{"class":1146},[1086,86225,1413],{"class":1146},[1086,86227,22856],{"class":1092},[1086,86229,1418],{"class":1146},[1086,86231,86232,86234,86236,86238,86240,86242,86244,86246,86248,86250,86252,86254,86256],{"class":1088,"line":3567},[1086,86233,4239],{"class":1423},[1086,86235,6327],{"class":1105},[1086,86237,1398],{"class":1146},[1086,86239,62279],{"class":1105},[1086,86241,861],{"class":1146},[1086,86243,8993],{"class":4109},[1086,86245,86137],{"class":1423},[1086,86247,47636],{"class":1105},[1086,86249,5931],{"class":1423},[1086,86251,23768],{"class":1436},[1086,86253,861],{"class":1146},[1086,86255,86072],{"class":4109},[1086,86257,1455],{"class":1146},[1572,86259,86260],{},[842,86261,86262,86263,86265,86266,86269,86270,86272,86273,86275],{},"Notice how the code reads like the business domain. A ",[895,86264,11183],{}," has ",[895,86267,86268],{},"Tracks",", a ",[895,86271,68442],{}," has an ",[895,86274,3856],{},". The ubiquitous language is embedded directly in the code.",[863,86277,86279],{"id":86278},"why-use-ddd","Why Use DDD?",[1045,86281,86283,86287,86291,86295],{"className":86282},[1048,1049,1765,1051,1052],[1054,86284],{"description":86285,"title":86286},"Developers and business stakeholders speak the same language. No more translating between \"business requirements\" and \"technical specs.\"","Better communication",[1054,86288],{"description":86289,"title":86290},"Bounded contexts keep complexity contained. Changes in one area don't cascade through the entire system.","Maintainability",[1054,86292],{"description":86293,"title":86294},"The software evolves with the business. When new rules are added, they map naturally to existing structures.","Business alignment",[1054,86296],{"description":86297,"title":86298},"Bounded contexts map cleanly to microservices. Each context can be deployed, scaled, and maintained independently.","Scalability",[863,86300,86302],{"id":86301},"when-ddd-makes-sense","When DDD Makes Sense",[842,86304,86305],{},"DDD is not for every project. It adds upfront complexity that pays off in large, evolving systems with complex business rules. A simple CRUD app doesn't need it.",[842,86307,86308],{},"DDD works best when:",[958,86310,86311,86314,86317,86320],{},[961,86312,86313],{},"The business domain is complex and has many rules",[961,86315,86316],{},"Multiple teams need to work on the same system without stepping on each other",[961,86318,86319],{},"The system will evolve significantly over time",[961,86321,86322],{},"Domain experts are available and willing to collaborate with developers",[863,86324,86326],{"id":86325},"about-mateusz-kubaszek","About Mateusz Kubaszek",[842,86328,86329,86332],{},[846,86330,85597],{"href":85595,"rel":86331},[850]," is a consultant, software architect, and data engineering architect with deep experience designing complex technical solutions. His presentation included live Python examples showing DDD patterns in practice.",[1032,86334,86335],{},[842,86336,86337,86338,86343],{},"If you're new to DDD, start with Eric Evans' ",[846,86339,86342],{"href":86340,"rel":86341},"https://www.dddcommunity.org/book/evans_2003/",[850],"Domain-Driven Design: Tackling Complexity in the Heart of Software"," — the book that started it all.",[1680,86345,86346],{},"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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .s5tWE, html code.shiki .s5tWE{--shiki-light:#E53935;--shiki-light-font-style:italic;--shiki-default:#F07178;--shiki-default-font-style:italic;--shiki-dark:#F07178;--shiki-dark-font-style:italic}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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);}",{"title":728,"searchDepth":729,"depth":729,"links":86348},[86349,86350,86351,86352,86353],{"id":85601,"depth":729,"text":85602},{"id":85624,"depth":729,"text":85625},{"id":86278,"depth":729,"text":86279},{"id":86301,"depth":729,"text":86302},{"id":86325,"depth":729,"text":86326},"2023-01-12T00:00:00.000Z","An introduction to Domain-Driven Design (DDD), an approach to software development that models business domains for more effective and maintainable systems.",{"src":86357},"/images/blog/musictechlab_blog_dev-meeting-002-introduction-to-domain-driven-design-ddd.webp",{"enabled":738,"items":86359},[86360,86362,86364,86366],{"text":86361,"icon":8737},"DDD structures code around the business domain, not technical layers.",{"text":86363,"icon":72284},"Ubiquitous Language ensures developers and domain experts use the same vocabulary.",{"text":86365,"icon":1769},"Bounded Contexts let a 'Track' mean different things in catalog vs. royalty systems.",{"text":86367,"icon":2895},"Aggregates group related entities into a single unit for atomic data changes.",{},{"title":422,"description":86355},[18784],"K0HkQQenT2trIMshOaDfnaS5ZNUIExysoT9XHOWM1m0",{"id":86373,"title":434,"authors":86374,"badge":723,"body":86377,"category":756,"client":723,"date":86482,"description":86483,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":86484,"keyTakeaways":86486,"meta":86496,"navigation":738,"path":435,"seo":86497,"status":723,"stem":436,"tags":86498,"teaser":723,"__hash__":86499},"posts/blog/software-development/dev-meeting-kubernetes-is-a-framework.md",[86375],{"name":834,"to":720,"avatar":86376},{"src":722},{"type":725,"value":86378,"toc":86476},[86379,86383,86386,86389,86404,86408,86419,86426,86430,86447,86456,86458,86465],[863,86380,86382],{"id":86381},"what-is-kubernetes","What is Kubernetes?",[842,86384,86385],{},"Kubernetes (K8s) is a portable, extensible, open-source platform for managing containerised workloads and services. It supports declarative configuration and automation. Google open-sourced the project in 2014, building on over 15 years of experience running production workloads at scale.",[842,86387,86388],{},"The name originates from Greek, meaning \"helmsman\" or \"pilot.\" K8s is shorthand — counting the eight letters between \"K\" and \"s.\"",[1045,86390,86392,86396,86400],{"className":86391},[1048,1049,1050,1051,1052],[1054,86393],{"description":86394,"title":86395},"Automates deployment, scaling, and management of containerised applications across clusters.","Container Orchestration",[1054,86397],{"description":86398,"title":86399},"Define desired state in YAML — Kubernetes ensures the cluster matches it.","Declarative Config",[1054,86401],{"description":86402,"title":86403},"Restarts failed containers, replaces nodes, and reschedules workloads automatically.","Self-Healing",[863,86405,86407],{"id":86406},"googles-problem-with-scale","Google's Problem with Scale",[958,86409,86410,86413,86416],{},[961,86411,86412],{},"Google ran millions of data jobs across datacenters — for search, indexing, and analytics",[961,86414,86415],{},"As the business expanded, managing multiple tenants and diverse workloads became increasingly difficult",[961,86417,86418],{},"They needed a standard way to schedule resources automatically at massive scale",[842,86420,86421,86422,86425],{},"This led to ",[996,86423,86424],{},"Borg",", Google's internal cluster manager — the direct predecessor to Kubernetes.",[863,86427,86429],{"id":86428},"how-kubernetes-was-born","How Kubernetes Was Born",[958,86431,86432,86435,86441,86444],{},[961,86433,86434],{},"Google extracted its resource management approach from Borg into an open-source project",[961,86436,86437,86438],{},"Together with Intel and Red Hat, they founded the ",[996,86439,86440],{},"Cloud Native Computing Foundation (CNCF)",[961,86442,86443],{},"CNCF became the governance body standardising container orchestration and the broader cloud-native ecosystem",[961,86445,86446],{},"Kubernetes quickly became the industry standard for deploying and managing containers",[1032,86448,86449],{},[842,86450,86451,86452,86455],{},"Kubernetes is not just a tool — it's a ",[996,86453,86454],{},"framework",". Through Operators and Custom Resource Definitions (CRDs), teams can extend it to manage any type of workload, from databases to ML pipelines.",[863,86457,25699],{"id":8196},[842,86459,86460,86461,86464],{},"Many thanks to ",[996,86462,86463],{},"Marcin Karkocha"," for presenting on Kubernetes at our Dev Meeting. The live demo showed theory in action — deploying and scaling containers in real time.",[1045,86466,86468,86472],{"className":86467},[13033,50238,50239,1052],[50241,86469],{"color":50243,"label":86470,"target":50245,"to":86471,"variant":50246},"Marcin Karkocha on LinkedIn","https://www.linkedin.com/in/mkarkocha/",[50241,86473],{"color":50249,"label":86474,"target":50245,"to":86475,"variant":50246},"Kubernetes Docs","https://kubernetes.io/docs/home/",{"title":728,"searchDepth":729,"depth":729,"links":86477},[86478,86479,86480,86481],{"id":86381,"depth":729,"text":86382},{"id":86406,"depth":729,"text":86407},{"id":86428,"depth":729,"text":86429},{"id":8196,"depth":729,"text":25699},"2023-01-05T00:00:00.000Z","Exploring Kubernetes as a framework: from Google's scaling challenges and Borg to Operators, CRDs, and modern container orchestration basics.",{"src":86485},"/images/blog/musictechlab_blog_dev-meeting-kubernetes-is-a-framework.webp",{"enabled":738,"items":86487},[86488,86490,86492,86494],{"text":86489,"icon":8737},"Kubernetes evolved from Google's internal Borg system after 15+ years of production use.",{"text":86491,"icon":1769},"It is a framework, not just a tool. CRDs and Operators extend it to any workload.",{"text":86493,"icon":7495},"Self-healing: auto-restarts failed containers and reschedules workloads across nodes.",{"text":86495,"icon":1067},"CNCF governs Kubernetes and the broader cloud-native ecosystem.",{},{"title":434,"description":86483},[15279,18784],"RcS-TiAWU6Us-_9EdUKIrZKVB0qAQggp7AK2p-0wOAo",{"id":86501,"title":310,"authors":86502,"badge":723,"body":86507,"category":756,"client":723,"date":86812,"description":86813,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":86814,"keyTakeaways":86816,"meta":86826,"navigation":738,"path":311,"seo":86827,"status":723,"stem":312,"tags":86828,"teaser":723,"__hash__":86829},"posts/blog/software-development/10-steps-to-find-the-best-mvp-developers-for-your-startup-idea.md",[86503],{"name":86504,"avatar":86505},"Mariusz Raczyński",{"src":86506},"/images/people/mariusz-raczynski.webp",{"type":725,"value":86508,"toc":86797},[86509,86511,86514,86546,86549,86552,86556,86559,86562,86573,86576,86579,86582,86585,86596,86599,86610,86613,86616,86619,86622,86636,86639,86643,86646,86657,86660,86664,86667,86678,86681,86684,86687,86698,86701,86704,86707,86718,86721,86724,86727,86730,86741,86744,86747,86750,86753,86764,86767,86770,86773,86776,86787,86790,86792,86795],[842,86510,40867],{},[863,86512,86513],{"id":84504},"Outline",[958,86515,86516,86519,86522,86525,86528,86531,86534,86537,86540,86543],{},[961,86517,86518],{},"Define your MVP (Minimum Viable Product) clearly",[961,86520,86521],{},"Determine your budget and timeline",[961,86523,86524],{},"Identify your technical requirements",[961,86526,86527],{},"Determine the type of developer you need (e.g. front-end, back-end, full-stack)",[961,86529,86530],{},"Consider your development approach (e.g. in-house, outsourced, hybrid)",[961,86532,86533],{},"Create a list of potential developers or development companies",[961,86535,86536],{},"Evaluate their experience and skills",[961,86538,86539],{},"Consider their communication and collaboration style",[961,86541,86542],{},"Check their references and reviews",[961,86544,86545],{},"Make a decision and get started on your MVP",[863,86547,32350],{"id":86548},"intro",[842,86550,86551],{},"As a startup founder, finding the right MVP developers is critical to the success of your product. Whether you're looking to build an in-house team or hire an outsourced development company, it's important to find developers who have the skills and experience to help bring your MVP to life. In this article, we'll provide 10 steps you can follow to find the best MVP developers for your startup idea, including tips on defining your MVP, determining your budget and timeline, identifying your technical requirements, and more. By following these steps, you'll be well on your way to finding the right developers to help bring your startup idea to life.",[863,86553,86555],{"id":86554},"define-your-mvp-clearly","Define your MVP clearly",[842,86557,86558],{},"Before you start searching for developers, it's important to have a clear understanding of what your MVP (Minimum Viable Product) entails. An MVP is a version of your product that has just enough features to allow users to experience its core functionality. It's designed to test a product hypothesis with the minimum amount of effort and resources, and to gather valuable feedback from early adopters.",[842,86560,86561],{},"To define your MVP, consider the following questions:",[958,86563,86564,86567,86570],{},[961,86565,86566],{},"What is the core problem that your product aims to solve?",[961,86568,86569],{},"What are the most essential features that your product needs to have in order to solve that problem?",[961,86571,86572],{},"Who is your target audience and what do they need from your product?",[842,86574,86575],{},"Having a clear definition of your MVP will help you communicate your vision to potential developers and allow them to better assess whether they have the skills and resources to help bring your product to life. It will also help you narrow down your search to developers who have relevant expertise and experience.",[863,86577,86521],{"id":86578},"determine-your-budget-and-timeline",[842,86580,86581],{},"Before you start reaching out to developers, it's important to have a good understanding of your budget and timeline constraints. This will help you determine the type of developer you can afford and the level of resources you have available to bring your MVP to fruition.",[842,86583,86584],{},"To determine your budget, consider the following factors:",[958,86586,86587,86590,86593],{},[961,86588,86589],{},"How much money do you have available to invest in development?",[961,86591,86592],{},"How much of your budget can you allocate to development?",[961,86594,86595],{},"Are you planning on seeking funding or investment to cover development costs?",[842,86597,86598],{},"To determine your timeline, consider the following factors:",[958,86600,86601,86604,86607],{},[961,86602,86603],{},"When do you need your MVP to be completed?",[961,86605,86606],{},"How long do you expect the development process to take?",[961,86608,86609],{},"Are there any external factors that might affect your timeline (e.g. regulatory approvals, market conditions)?",[842,86611,86612],{},"Having a clear understanding of your budget and timeline will help you communicate your constraints to potential developers and allow them to better assess whether they can meet your needs. It will also help you prioritize your MVP features and make informed decisions about which features to include and which to potentially cut or delay.",[863,86614,86524],{"id":86615},"identify-your-technical-requirements",[842,86617,86618],{},"To find the best MVP developers for your startup idea, you'll need to have a clear understanding of your technical requirements. This includes both the specific technologies and frameworks you need for your MVP, as well as any other technical considerations such as scalability and security.",[842,86620,86621],{},"To identify your technical requirements, consider the following questions:",[958,86623,86624,86627,86630,86633],{},[961,86625,86626],{},"What specific technologies and frameworks does your MVP need to be built with?",[961,86628,86629],{},"Will your MVP require integration with any external APIs or services?",[961,86631,86632],{},"Will your MVP need to be scalable to handle a large number of users or a high volume of data?",[961,86634,86635],{},"What security measures will your MVP need to have in place?",[842,86637,86638],{},"Having a clear list of your technical requirements will help you communicate your needs to potential developers and allow them to better assess whether they have the skills and experience to build your MVP. It will also help you narrow down your search to developers who have relevant expertise and experience.",[863,86640,86642],{"id":86641},"determine-the-type-of-developer-you-need","Determine the type of developer you need",[842,86644,86645],{},"Once you have a clear understanding of your MVP and technical requirements, you'll need to determine the type of developer you need to bring your idea to life. There are several types of developers you can consider, including:",[958,86647,86648,86651,86654],{},[961,86649,86650],{},"Front-end developers: These developers specialize in building the user-facing part of your product. They typically work with technologies such as HTML, CSS, and JavaScript to create the layout, design, and interactive features of your MVP.",[961,86652,86653],{},"Back-end developers: These developers specialize in building the server-side of your product. They typically work with technologies such as PHP, Python, and Ruby to create the database, server-side logic, and APIs of your MVP.",[961,86655,86656],{},"Full-stack developers: These developers have expertise in both front-end and back-end development. They can build both the user-facing and server-side of your product, making them a good choice for MVPs that require a more integrated development approach.",[842,86658,86659],{},"Consider the specific technical requirements of your MVP and determine which type of developer (or developers) you'll need to bring your idea to life. Having a clear understanding of the type of developer you need will help you narrow down your search and find the best fit for your team.",[863,86661,86663],{"id":86662},"consider-your-development-approach","Consider your development approach",[842,86665,86666],{},"There are several approaches you can take to developing your MVP, each with its own set of pros and cons. Some of the most common approaches include:",[958,86668,86669,86672,86675],{},[961,86670,86671],{},"In-house development: This approach involves hiring a team of developers to work on your MVP full-time. This can be a good option if you have a large budget, need a high level of control over the development process, or want to build a long-term development team.",[961,86673,86674],{},"Outsourced development: This approach involves hiring a development company or a team of freelancers to work on your MVP. This can be a good option if you have a limited budget, need a quick turnaround, or don't have the resources to build an in-house team.",[961,86676,86677],{},"Hybrid development: This approach involves a combination of in-house and outsourced development. This can be a good option if you have specific parts of your MVP that you want to handle in-house and other parts that you want to outsource.",[842,86679,86680],{},"Consider the specific needs and constraints of your MVP and determine which development approach is the best fit for you. Having a clear understanding of your development approach will help you narrow down your search and find the right developers for your project.",[863,86682,86533],{"id":86683},"create-a-list-of-potential-developers-or-development-companies",[842,86685,86686],{},"Once you have a clear understanding of your MVP, technical requirements, and development approach, it's time to start building a list of potential developers or development companies. There are several ways you can do this, including:",[958,86688,86689,86692,86695],{},[961,86690,86691],{},"Searching online directories or job boards for developers or development companies that specialize in the technologies and frameworks you need.",[961,86693,86694],{},"Asking for recommendations from your network, including friends, colleagues, or industry experts.",[961,86696,86697],{},"Attending industry events or meetups where you can meet and connect with developers or development companies.",[842,86699,86700],{},"Create a list of potential developers or development companies that you think might be a good fit for your MVP. Make sure to include a mix of options, including both large and small companies, and both in-house and outsourced developers. Having a diverse list will give you a range of options to consider and help you find the best fit for your MVP.",[863,86702,86536],{"id":86703},"evaluate-their-experience-and-skills",[842,86705,86706],{},"Once you have a list of potential developers or development companies, it's important to evaluate their experience and skills to determine whether they are a good fit for your MVP. There are several factors you can consider when evaluating their experience and skills, including:",[958,86708,86709,86712,86715],{},[961,86710,86711],{},"Relevant experience: Look for developers or development companies that have experience building MVPs or similar products. This will give you a sense of their capabilities and whether they have the expertise to build your MVP.",[961,86713,86714],{},"Portfolio: Review their portfolio of previous work to get a sense of the type and quality of products they have built in the past. This can give you a good idea of what to expect from them and whether their style and approach align with your vision.",[961,86716,86717],{},"Skills: Look for developers or development companies that have the specific skills and expertise you need for your MVP. This might include specific technologies or frameworks, as well as more general skills such as problem-solving and collaboration.",[842,86719,86720],{},"Evaluating the experience and skills of potential developers or development companies will help you narrow down your list and find the best fit for your MVP. It will also help you make an informed decision about which developer or development company to hire.",[863,86722,86539],{"id":86723},"consider-their-communication-and-collaboration-style",[842,86725,86726],{},"In addition to evaluating the experience and skills of potential developers or development companies, it's important to consider their communication and collaboration style. Building an MVP involves a lot of back-and-forth between the development team and the rest of your team, so it's important to find developers or development companies that have a communication and collaboration style that aligns with your needs.",[842,86728,86729],{},"Some things you can consider when evaluating communication and collaboration style include:",[958,86731,86732,86735,86738],{},[961,86733,86734],{},"Availability: Look for developers or development companies that are responsive and available to communicate with you. This will be especially important during the development process, when you'll need to be in close communication with them.",[961,86736,86737],{},"Transparency: Look for developers or development companies that are transparent and open about their work. This might include sharing their development process, providing regular updates, and being open to feedback and suggestions.",[961,86739,86740],{},"Team fit: Consider whether the developer or development company will be a good fit with your team. Building an MVP is a collaborative process, so it's important to find developers or development companies that are compatible with your team's culture and way of working.",[842,86742,86743],{},"Finding developers or development companies with a communication and collaboration style that aligns with your needs will help ensure a smooth and successful development process. It will also help build a strong foundation for future collaboration.",[863,86745,86542],{"id":86746},"check-their-references-and-reviews",[842,86748,86749],{},"Before making a decision about which developer or development company to hire, it's a good idea to check their references and reviews. This will give you a sense of their past work and help you gauge the quality of their work and their ability to deliver on their promises.",[842,86751,86752],{},"Some things you can do to check references and reviews include:",[958,86754,86755,86758,86761],{},[961,86756,86757],{},"Ask for references: Most developers and development companies will be happy to provide references upon request. Contact their past clients and ask about their experience working with the developer or development company.",[961,86759,86760],{},"Read online reviews: Look for online reviews of the developer or development company. These can be found on websites such as LinkedIn, Glassdoor, or Clutch.",[961,86762,86763],{},"Check their social media profiles: Look for reviews or testimonials on the developer or development company's social media profiles.",[842,86765,86766],{},"Checking references and reviews will help you get a sense of the developer or development company's track record and help you make an informed decision about which one to hire.",[863,86768,86545],{"id":86769},"make-a-decision-and-get-started-on-your-mvp",[842,86771,86772],{},"Once you have evaluated the experience, skills, communication, and collaboration style of potential developers or development companies, and checked their references and reviews, it's time to make a decision and get started on your MVP.",[842,86774,86775],{},"Consider the following factors when making your decision:",[958,86777,86778,86781,86784],{},[961,86779,86780],{},"Fit: Choose the developer or development company that you feel is the best fit for your MVP and your team.",[961,86782,86783],{},"Cost: Consider the cost of each option and choose the one that fits within your budget.",[961,86785,86786],{},"Timeline: Choose the option that can deliver your MVP within your desired timeline.",[842,86788,86789],{},"Once you have made a decision, it's time to get started on your MVP. Work with your chosen developer or development company to establish a development plan, set clear goals and milestones, and get started on building your MVP. With a clear plan in place and the right team in place, you'll be well on your way to bringing your startup idea to life.",[863,86791,18681],{"id":18680},[842,86793,86794],{},"Finding the right MVP developers for your startup idea is an essential step in the process of bringing your product to life. By defining your MVP clearly, determining your budget and timeline, identifying your technical requirements, and considering your development approach, you can narrow down your search and find the best fit for your team. By evaluating the experience and skills of potential developers or development companies, considering their communication and collaboration style, and checking their references and reviews, you can make an informed decision and get started on your MVP with confidence. With the right developers in place, you'll be well on your way to building a successful product that meets the needs of your users and helps you achieve your business goals.",[842,86796,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":86798},[86799,86800,86801,86802,86803,86804,86805,86806,86807,86808,86809,86810,86811],{"id":84504,"depth":729,"text":86513},{"id":86548,"depth":729,"text":32350},{"id":86554,"depth":729,"text":86555},{"id":86578,"depth":729,"text":86521},{"id":86615,"depth":729,"text":86524},{"id":86641,"depth":729,"text":86642},{"id":86662,"depth":729,"text":86663},{"id":86683,"depth":729,"text":86533},{"id":86703,"depth":729,"text":86536},{"id":86723,"depth":729,"text":86539},{"id":86746,"depth":729,"text":86542},{"id":86769,"depth":729,"text":86545},{"id":18680,"depth":729,"text":18681},"2022-12-16T00:00:00.000Z","How to find skilled MVP developers for your startup, whether building an in-house team or hiring an outsourced development company.",{"src":86815},"/images/blog/musictechlab_blog_10-steps-to-find-the-best-mvp-developers-for-your-startup-idea.webp",{"enabled":738,"items":86817},[86818,86820,86822,86824],{"text":86819,"icon":50270},"Define your MVP scope before searching for developers to avoid wasted effort.",{"text":86821,"icon":5504},"Budget, timeline, and technical requirements narrow down the right partner fast.",{"text":86823,"icon":4845},"Check references and past portfolios, not just skills listed on a profile.",{"text":86825,"icon":11617},"Consider a trial project before committing to a long-term engagement.",{},{"title":310,"description":86813},[74615],"J0yy7EbhAiRJAwbBWXkv2aHJ5l8dwD6Z6paHjWrnaV4",{"id":86831,"title":322,"authors":86832,"badge":723,"body":86835,"category":756,"client":723,"date":87058,"description":87059,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":87060,"keyTakeaways":87062,"meta":87073,"navigation":738,"path":323,"seo":87074,"status":723,"stem":324,"tags":87075,"teaser":723,"__hash__":87076},"posts/blog/software-development/7-best-practices-for-outsourcing-software-development.md",[86833],{"name":86504,"avatar":86834},{"src":86506},{"type":725,"value":86836,"toc":87046},[86837,86839,86841,86864,86866,86869,86872,86875,86878,86881,86892,86895,86898,86901,86904,86918,86921,86924,86927,86930,86941,86944,86947,86950,86953,86967,86970,86973,86976,86979,86990,86993,86996,86999,87002,87013,87016,87019,87022,87025,87036,87039,87041,87044],[842,86838,40867],{},[863,86840,86513],{"id":84504},[958,86842,86843,86846,86849,86852,86855,86858,86861],{},[961,86844,86845],{},"Define your project scope and objectives clearly",[961,86847,86848],{},"Research and select the right development partner",[961,86850,86851],{},"Set clear expectations and establish open communication",[961,86853,86854],{},"Use a project management tool to stay organized and on track",[961,86856,86857],{},"Establish clear milestones and deliverables",[961,86859,86860],{},"Set up a payment schedule that works for both parties",[961,86862,86863],{},"Consider a trial period or pilot project",[863,86865,32350],{"id":86548},[842,86867,86868],{},"Outsourcing software development can be a cost-effective way to bring your product to life, especially if you don't have the resources or expertise to build it in-house. However, outsourcing can also present its own set of challenges, including communication barriers, cultural differences, and time zone differences. To ensure a smooth and successful development process, it's important to follow best practices for outsourcing software development. In this article, we'll provide 7 best practices that you can follow to set yourself up for success when outsourcing your software development project. By following these best practices, you can build a strong partnership with your development partner and bring your product to life efficiently and effectively.",[863,86870,86845],{"id":86871},"define-your-project-scope-and-objectives-clearly",[842,86873,86874],{},"Before you start outsourcing your software development project, it's important to have a clear understanding of your project scope and objectives. This includes defining the specific features and functionality that you want to include in your product, as well as your target audience and business goals.",[842,86876,86877],{},"Having a clear definition of your project scope and objectives will help you communicate your vision to potential development partners and allow them to better assess whether they have the skills and resources to help bring your product to life. It will also help you narrow down your search to development partners who have relevant expertise and experience.",[842,86879,86880],{},"To define your project scope and objectives, consider the following questions:",[958,86882,86883,86885,86887,86889],{},[961,86884,86566],{},[961,86886,86569],{},[961,86888,86572],{},[961,86890,86891],{},"What are your business goals for your product (e.g. revenue, user growth, market share)?",[842,86893,86894],{},"By answering these questions, you can create a clear and detailed specification for your project that will serve as a roadmap for your development partner. This will help ensure that your project stays on track and meets your expectations.",[863,86896,86848],{"id":86897},"research-and-select-the-right-development-partner",[842,86899,86900],{},"Once you have a clear understanding of your project scope and objectives, it's time to start researching and selecting the right development partner. This can be a challenging task, as there are many development partners to choose from, each with their own strengths and weaknesses.",[842,86902,86903],{},"To find the right development partner, consider the following factors:",[958,86905,86906,86909,86912,86915],{},[961,86907,86908],{},"Experience: Look for development partners that have experience building similar products or working in your industry. This will give you a sense of their capabilities and whether they have the expertise to build your product.",[961,86910,86911],{},"Portfolio: Review the development partner's portfolio of previous work to get a sense of the type and quality of products they have built in the past. This can give you a good idea of what to expect from them and whether their style and approach align with your vision.",[961,86913,86914],{},"Skills: Look for development partners that have the specific skills and expertise you need for your project. This might include specific technologies or frameworks, as well as more general skills such as problem-solving and collaboration.",[961,86916,86917],{},"Location: Consider whether location is a factor for your project. If you need to work closely with your development partner or have frequent face-to-face meetings, it may be beneficial to choose a development partner that is located nearby. If location is not a factor, you can consider development partners from anywhere in the world.",[842,86919,86920],{},"By considering these factors, you can narrow down your search and find a development partner that is the best fit for your project. Remember to also consider the cost of each option and choose the one that fits within your budget.",[863,86922,86851],{"id":86923},"set-clear-expectations-and-establish-open-communication",[842,86925,86926],{},"Effective communication is key to the success of any outsourcing project, and setting clear expectations and establishing open lines of communication is essential to ensuring a smooth and successful development process.",[842,86928,86929],{},"To set clear expectations and establish open communication, consider the following best practices:",[958,86931,86932,86935,86938],{},[961,86933,86934],{},"Create a detailed specification for your project: A detailed specification will serve as a roadmap for your development partner and help ensure that your project stays on track and meets your expectations. Be sure to include a clear list of features, functionality, and any other requirements or constraints.",[961,86936,86937],{},"Use a project management tool: A project management tool can help you keep track of your project, assign tasks, and communicate with your development partner. There are many tools available, such as Asana, Trello, or JIRA, which can help you stay organized and on track.",[961,86939,86940],{},"Schedule regular meetings: Schedule regular meetings with your development partner to discuss progress, address any issues or concerns, and make any necessary adjustments. These meetings can be in-person, over the phone, or via video conferencing, depending on your preferences and location.",[842,86942,86943],{},"By setting clear expectations and establishing open communication, you can build a strong partnership with your development partner and ensure a smooth and successful development process.",[863,86945,86854],{"id":86946},"use-a-project-management-tool-to-stay-organized-and-on-track",[842,86948,86949],{},"A project management tool can be a valuable asset when outsourcing software development, as it can help you stay organized, assign tasks, and communicate with your development partner. There are many project management tools available, each with its own set of features and capabilities.",[842,86951,86952],{},"To choose the right project management tool for your project, consider the following factors:",[958,86954,86955,86958,86961,86964],{},[961,86956,86957],{},"Ease of use: Look for a tool that is easy to use and navigate, as you'll be using it on a daily basis.",[961,86959,86960],{},"Collaboration features: Choose a tool that has robust collaboration features, such as the ability to assign tasks, leave comments, and track progress.",[961,86962,86963],{},"Customization: Look for a tool that allows you to customize your project view and organization. This will help you tailor the tool to your specific needs and workflow.",[961,86965,86966],{},"Integration with other tools: Consider whether the tool integrates with other tools you use, such as your email client or CRM. This can help streamline your work and improve efficiency.",[842,86968,86969],{},"By using a project management tool, you can stay organized and on track throughout the development process and ensure that your project stays on schedule and meets your expectations.",[863,86971,86857],{"id":86972},"establish-clear-milestones-and-deliverables",[842,86974,86975],{},"To ensure that your outsourcing project stays on track and meets your expectations, it's important to establish clear milestones and deliverables. These can include specific features or functionality that you want to include in your product, as well as deadlines for completing each milestone.",[842,86977,86978],{},"To establish clear milestones and deliverables, consider the following best practices:",[958,86980,86981,86984,86987],{},[961,86982,86983],{},"Break your project into smaller chunks: Instead of trying to build your entire product all at once, break your project into smaller chunks or phases. This will help you stay focused and avoid overwhelming your development partner.",[961,86985,86986],{},"Set clear deadlines: Establish clear deadlines for each milestone and deliverable. This will help you stay on track and ensure that your project stays on schedule.",[961,86988,86989],{},"Use a project management tool: A project management tool can help you track your milestones and deliverables, assign tasks, and communicate with your development partner.",[842,86991,86992],{},"By establishing clear milestones and deliverables, you can stay organized and on track throughout the development process and ensure that your project stays on schedule and meets your expectations.",[863,86994,86860],{"id":86995},"set-up-a-payment-schedule-that-works-for-both-parties",[842,86997,86998],{},"When outsourcing software development, it's important to set up a payment schedule that works for both parties. This might include upfront payments, milestone payments, or a combination of both.",[842,87000,87001],{},"To set up a payment schedule that works for both parties, consider the following best practices:",[958,87003,87004,87007,87010],{},[961,87005,87006],{},"Negotiate the terms of your payment schedule: Before you start your project, negotiate the terms of your payment schedule with your development partner. This might include the amount of each payment, the frequency of payments, and the specific milestones or deliverables that trigger each payment.",[961,87008,87009],{},"Use a contract: Use a contract to formalize your payment schedule and protect both parties. A contract should include details about the scope of work, the payment schedule, and any other terms and conditions.",[961,87011,87012],{},"Use a payment gateway: Use a payment gateway to securely and easily process payments. This might include a service such as PayPal, Stripe, or 2Checkout.",[842,87014,87015],{},"By setting up a payment schedule that works for both parties and using a contract and payment gateway, you can protect yourself and your development partner and ensure that your project stays on track and meets your expectations.",[863,87017,86863],{"id":87018},"consider-a-trial-period-or-pilot-project",[842,87020,87021],{},"If you're considering outsourcing software development for the first time, or if you're working with a new development partner, it might be a good idea to consider a trial period or pilot project. This can help you test the waters and see if the development partner is a good fit for your project before committing to a longer-term partnership.",[842,87023,87024],{},"To set up a trial period or pilot project, consider the following best practices:",[958,87026,87027,87030,87033],{},[961,87028,87029],{},"Define the scope of the trial period or pilot project: Clearly define the scope of the trial period or pilot project, including the specific features or functionality that you want to include.",[961,87031,87032],{},"Set clear expectations: Establish clear expectations for the trial period or pilot project, including deadlines and deliverables.",[961,87034,87035],{},"Use a project management tool: Use a project management tool to track progress and communicate with your development partner.",[842,87037,87038],{},"By setting up a trial period or pilot project, you can test the waters and see if the development partner is a good fit for your project before committing to a longer-term partnership. This can help you build a strong foundation for future collaboration and ensure a smooth and successful development process.",[863,87040,18681],{"id":18680},[842,87042,87043],{},"From our estimates almost 30% of companies are outsourcing IT services, out of which 78% are satisfied. Outsourcing software development can be a cost-effective way to bring your product to life, but it's important to be organized and proactive to ensure a smooth and successful development process. By following best practices such as defining your project scope and objectives clearly, researching and selecting the right development partner, setting clear expectations and establishing open communication, using a project management tool, establishing clear milestones and deliverables, setting up a payment schedule that works for both parties, and considering a trial period or pilot project, you can set yourself up for success and build a strong partnership with your development partner. By following these best practices, you can bring your product to life efficiently and effectively, and achieve your business goals.",[842,87045,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":87047},[87048,87049,87050,87051,87052,87053,87054,87055,87056,87057],{"id":84504,"depth":729,"text":86513},{"id":86548,"depth":729,"text":32350},{"id":86871,"depth":729,"text":86845},{"id":86897,"depth":729,"text":86848},{"id":86923,"depth":729,"text":86851},{"id":86946,"depth":729,"text":86854},{"id":86972,"depth":729,"text":86857},{"id":86995,"depth":729,"text":86860},{"id":87018,"depth":729,"text":86863},{"id":18680,"depth":729,"text":18681},"2022-12-13T00:00:00.000Z","In this article, we'll provide 7 best practices that you can follow to set yourself up for success when outsourcing your software development project.",{"src":87061},"/images/blog/musictechlab_blog_7-best-practices-for-outsourcing-software-development.webp",{"enabled":738,"items":87063},[87064,87066,87069,87071],{"text":87065,"icon":50270},"Define project scope and objectives clearly before contacting any vendor.",{"text":87067,"icon":87068},"Use a project management tool like Jira or Trello for shared visibility.","i-lucide-wrench",{"text":87070,"icon":11617},"Start with a trial period or pilot project to test the partnership.",{"text":87072,"icon":72284},"Schedule regular meetings to catch issues early and keep alignment.",{},{"title":322,"description":87059},[74615],"fWOIq8Rg8GsULQ8a49AlYOHZYOLV7zGFfL54gxjP52s",{"id":87078,"title":498,"authors":87079,"badge":723,"body":87082,"category":756,"client":723,"date":87332,"description":87333,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":87334,"keyTakeaways":87336,"meta":87344,"navigation":738,"path":499,"seo":87345,"status":723,"stem":500,"tags":87346,"teaser":723,"__hash__":87347},"posts/blog/software-development/how-to-build-a-minimum-viable-product-mvp-in-6-steps.md",[87080],{"name":86504,"avatar":87081},{"src":86506},{"type":725,"value":87083,"toc":87323},[87084,87091,87094,87121,87123,87129,87133,87136,87157,87159,87163,87166,87182,87187,87189,87193,87196,87211,87214,87216,87220,87223,87238,87240,87246,87250,87253,87269,87274,87276,87280,87283,87299,87302,87304,87306,87309,87314,87317],[842,87085,87086,87087,87090],{},"Starting a new product can be an exciting and daunting prospect. It's a chance to bring a new idea to life — but it can also be risky and time-consuming to build a fully-featured product, especially if it doesn't succeed in the market. That's where the concept of a ",[996,87088,87089],{},"minimum viable product (MVP)"," comes in.",[842,87092,87093],{},"An MVP is a stripped-down version of a product that includes only the most essential features. The goal is to validate an idea quickly and inexpensively, gathering feedback from users and making iterative improvements along the way.",[1045,87095,87097,87101,87105,87109,87113,87117],{"className":87096},[1048,1049,1050,1051,1052],[1054,87098],{"description":87099,"icon":3181,"title":87100},"Identify the core problem, target audience, and essential features.","1. Define",[1054,87102],{"description":87103,"icon":7560,"title":87104},"Conduct market research, talk to users, test prototypes.","2. Research & Validate",[1054,87106],{"description":87107,"icon":4845,"title":87108},"Designers, developers, marketers — the skills you need.","3. Assemble Your Team",[1054,87110],{"description":87111,"icon":13562,"title":87112},"Choose your stack, version control, and team workflow.","4. Set Up Dev Environment",[1054,87114],{"description":87115,"icon":66072,"title":87116},"Iterate fast — test early, fix bugs, gather feedback.","5. Build & Test",[1054,87118],{"description":87119,"icon":37696,"title":87120},"Ship it, collect user feedback, keep improving.","6. Launch & Iterate",[4937,87122],{},[842,87124,87125],{},[1027,87126],{"alt":87127,"src":87128},"Team planning product features on a whiteboard with sticky notes","/images/blog/musictechlab_blog_mvp-whiteboard-planning.webp",[863,87130,87132],{"id":87131},"step-1-define-your-mvp","Step 1: Define Your MVP",[842,87134,87135],{},"Defining your MVP is critical to the success of your product. It helps you focus on the most important aspects and avoid unnecessary features that distract from the core value proposition.",[1045,87137,87139,87144,87148,87153],{"className":87138},[1048,1049,1765,1051,1052],[1054,87140],{"description":87141,"icon":87142,"title":87143},"Identify the core problem your product will address. Be specific — vague problems lead to vague products.","i-lucide-help-circle","What Problem Do You Solve?",[1054,87145],{"description":87146,"icon":84325,"title":87147},"Define your ideal user. The more you know about them, the better you can prioritize features.","Who Is Your Target Audience?",[1054,87149],{"description":87150,"icon":87151,"title":87152},"List the minimum features needed to solve the problem and meet your audience's needs.","i-lucide-check-square","What's Essential?",[1054,87154],{"description":87155,"icon":3271,"title":87156},"Identify non-essential features that can be deferred to later iterations.","What Can Wait?",[4937,87158],{},[863,87160,87162],{"id":87161},"step-2-research-and-validate","Step 2: Research and Validate",[842,87164,87165],{},"Before you start building, ensure there is market demand for your product and that it addresses a real need.",[1045,87167,87169,87173,87177],{"className":87168},[1048,1049,1050,1051,1052],[1054,87170],{"description":87171,"icon":3844,"title":87172},"Analyze the competitive landscape, identify gaps, and understand what already exists.","Market Research",[1054,87174],{"description":87175,"icon":72284,"title":87176},"Conduct interviews with potential customers to validate assumptions about their needs.","Talk to Users",[1054,87178],{"description":87179,"icon":87180,"title":87181},"Build mockups or wireframes and test them with a small group to gather early feedback.","i-lucide-layout-template","Test Prototypes",[1572,87183,87184],{},[842,87185,87186],{},"By conducting research and gathering feedback from potential users, you can make informed decisions and increase the chances of success once your MVP is launched.",[4937,87188],{},[863,87190,87192],{"id":87191},"step-3-assemble-your-team","Step 3: Assemble Your Team",[842,87194,87195],{},"Depending on the complexity of your MVP, you may need a team of professionals to help you build and launch it.",[1045,87197,87199,87203,87207],{"className":87198},[1048,1049,1050,1051,1052],[1054,87200],{"description":87201,"icon":5507,"title":87202},"Create the look and feel of your product — UI, UX, and brand identity.","Designers",[1054,87204],{"description":87205,"icon":5365,"title":87206},"Build the software — frontend, backend, infrastructure, and integrations.","Developers",[1054,87208],{"description":87209,"icon":82787,"title":87210},"Promote and sell your MVP once it's launched — positioning, messaging, and growth.","Marketers",[842,87212,87213],{},"This might involve hiring full-time employees, contracting freelancers, or working with a development agency. Whatever approach you take, have a clear idea of what roles and responsibilities are needed.",[4937,87215],{},[863,87217,87219],{"id":87218},"step-4-set-up-your-development-environment","Step 4: Set Up Your Development Environment",[842,87221,87222],{},"Setting up your development environment ensures that your team can work efficiently and effectively.",[1045,87224,87226,87230,87234],{"className":87225},[1048,1049,1050,1051,1052],[1054,87227],{"description":87228,"icon":31803,"title":87229},"Choose a language suitable for your needs — Python, JavaScript, Dart, Rust, or others depending on the product type.","Programming Language",[1054,87231],{"description":87232,"icon":1779,"title":87233},"Set up Git (or similar) to track changes to your code and collaborate with your team.","Version Control",[1054,87235],{"description":87236,"icon":8805,"title":87237},"Establish how code is reviewed and merged, how bugs are tracked, and how releases are managed.","Team Workflow",[4937,87239],{},[842,87241,87242],{},[1027,87243],{"alt":87244,"src":87245},"Two developers collaborating on code at their workstations","/images/blog/musictechlab_blog_mvp-team-collaboration.webp",[863,87247,87249],{"id":87248},"step-5-build-and-test","Step 5: Build and Test",[842,87251,87252],{},"Building and testing your MVP is an iterative process that involves trial and error.",[1045,87254,87256,87260,87265],{"className":87255},[1048,1049,1050,1051,1052],[1054,87257],{"description":87258,"icon":84309,"title":87259},"Define what you want to accomplish and what features are needed to achieve those goals. Track your progress against milestones.","Set Clear Goals",[1054,87261],{"description":87262,"icon":87263,"title":87264},"Don't wait until your MVP is complete. Test frequently to identify and fix issues as they arise.","i-lucide-test-tubes","Test Early and Often",[1054,87266],{"description":87267,"icon":52268,"title":87268},"Use feedback from users to iterate and improve. Don't be afraid to pivot if needed.","Gather Feedback & Iterate",[1901,87270,87271],{},[842,87272,87273],{},"Don't fall into the trap of building in silence. The longer you go without user feedback, the higher the risk of building something nobody wants.",[4937,87275],{},[863,87277,87279],{"id":87278},"step-6-launch-and-iterate","Step 6: Launch and Iterate",[842,87281,87282],{},"Launching your MVP is an exciting and nerve-wracking experience. It's a chance to bring your product to market and see how it performs.",[1045,87284,87286,87290,87294],{"className":87285},[1048,1049,1050,1051,1052],[1054,87287],{"description":87288,"icon":84330,"title":87289},"Ask users structured questions about their experience, pain points, and feature requests.","Conduct Surveys",[1054,87291],{"description":87292,"icon":3649,"title":87293},"Track how users interact with your product — what they use, what they ignore, where they drop off.","Analyze Usage Data",[1054,87295],{"description":87296,"icon":87297,"title":87298},"Direct conversations reveal insights that analytics alone can't capture.","i-lucide-phone","Talk to Customers",[842,87300,87301],{},"Use this feedback to iterate on your MVP and continue improving over time. Don't be afraid to make changes and pivot — the goal of an MVP is to learn from your users and make iterative improvements.",[4937,87303],{},[863,87305,18681],{"id":18680},[842,87307,87308],{},"Building an MVP is a powerful way to validate a product idea quickly and inexpensively. The key takeaway:",[1032,87310,87311],{},[842,87312,87313],{},"The goal of an MVP is to learn from your users and make iterative improvements. Don't be afraid to pivot — the beauty of an MVP is that it allows you to test and learn before investing too much time and resources into a full-scale product.",[842,87315,87316],{},"At MusicTech Lab, we've helped teams go from idea to MVP across music tech, creative industries, and beyond. The key is starting small, validating fast, and iterating with real user feedback.",[1045,87318,87320],{"className":87319},[13033,50238,50239,1052],[50241,87321],{"color":50243,"label":87322,"target":50245,"to":4946,"variant":50246},"Build Your MVP with Us",{"title":728,"searchDepth":729,"depth":729,"links":87324},[87325,87326,87327,87328,87329,87330,87331],{"id":87131,"depth":729,"text":87132},{"id":87161,"depth":729,"text":87162},{"id":87191,"depth":729,"text":87192},{"id":87218,"depth":729,"text":87219},{"id":87248,"depth":729,"text":87249},{"id":87278,"depth":729,"text":87279},{"id":18680,"depth":729,"text":18681},"2022-12-11T00:00:00.000Z","Starting a new product can be an exciting and daunting prospect. On one hand, it's a chance to bring a new idea to life and potentially change the world.",{"src":87335},"/images/blog/musictechlab_blog_how-to-build-a-minimum-viable-product-mvp-in-6-steps.webp",{"enabled":738,"items":87337},[87338,87340,87342],{"text":87339,"icon":37696},"An MVP includes only essential features to validate a product idea quickly and cheaply.",{"text":87341,"icon":50622},"Six steps: define, research, assemble team, set up dev environment, build and test, launch.",{"text":87343,"icon":72284},"Testing early and gathering user feedback prevents building features nobody wants.",{},{"title":498,"description":87333},[74615,18784],"XDqCp7CK1agS8c-QJyItF3_teFxDbhTMuylKjXOikU4",{"id":87349,"title":42,"authors":723,"badge":87350,"body":87351,"category":4990,"client":87590,"date":87592,"description":87593,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":87594,"keyTakeaways":87596,"meta":87606,"navigation":738,"path":43,"seo":87607,"status":723,"stem":44,"tags":87608,"teaser":723,"__hash__":87609},"posts/blog/case-study/panther-pricing-recommendation-tool-case-study.md",{"label":5,"color":50099},{"type":725,"value":87352,"toc":87578},[87353,87356,87359,87361,87365,87368,87370,87372,87389,87391,87393,87403,87405,87407,87410,87425,87428,87430,87434,87437,87440,87442,87444,87447,87467,87469,87473,87493,87495,87499,87504,87510,87515,87521,87527,87529,87533,87536,87563,87568,87570,87572],[842,87354,87355],{},"Small and medium shops are facing intense competition in the market. The post-pandemic era has caused all retail industries to need online tools with automation systems to compete and survive. Working with data in real-time has become one of the essential solutions for every company to make better choices.",[842,87357,87358],{},"MusicTech Lab partnered with Panther Pricing to build a cloud platform that generates automated price recommendations for retailers in Germany — allowing brick-and-mortar companies to eliminate negative margin contributions from online sales.",[4937,87360],{},[863,87362,87364],{"id":87363},"about-panther-pricing","About Panther Pricing",[842,87366,87367],{},"Panther Pricing is a German company founded in 2018 and based in Frankfurt, dedicated to providing innovative online services to boost retailers in Germany.",[4937,87369],{},[863,87371,65386],{"id":65385},[1045,87373,87376,87378,87380,87382,87385],{"className":87374},[1048,50574,1050,87375,50239,1052],"lg:grid-cols-5",[1054,87377],{"description":2169,"icon":2895,"title":65403},[1054,87379],{"description":72153,"icon":6395,"title":72154},[1054,87381],{"description":72161,"icon":72248,"title":72162},[1054,87383],{"description":87384,"icon":2939,"title":64259},"Modern Python web framework",[1054,87386],{"description":87387,"icon":1769,"title":87388},"Python SQL toolkit","SQLAlchemy",[4937,87390],{},[863,87392,66002],{"id":66001},[1045,87394,87396,87399],{"className":87395},[1048,1049,1765,1051,1052],[1054,87397],{"description":87398,"icon":5365,"title":72178},"Full-stack development — Vue.js frontend and Python/FastAPI backend.",[1054,87400],{"description":87401,"icon":4845,"title":87402},"Embedded developers working alongside the client's internal team.","IT Staff Augmentation",[4937,87404],{},[863,87406,52035],{"id":52034},[842,87408,87409],{},"Panther Pricing encountered three major challenges:",[1045,87411,87413,87417,87421],{"className":87412},[1048,1049,1050,1051,1052],[1054,87414],{"description":87415,"icon":50617,"title":87416},"Enhance system performance and scalability to handle growing data volumes.","System Performance",[1054,87418],{"description":87419,"icon":11614,"title":87420},"Augment knowledge and expertise that was internally lacking.","Missing Expertise",[1054,87422],{"description":87423,"icon":3271,"title":87424},"Urgency — no time for prolonged onboarding, results needed immediately.","Time Sensitivity",[842,87426,87427],{},"The client sought a reliable technology partner possessing specific expertise, ready to assist their internal development team. Given the urgency, they anticipated results at the earliest opportunity.",[4937,87429],{},[863,87431,87433],{"id":87432},"client-engagement-process","Client Engagement Process",[3572,87435],{":items":87436},"[{\"title\":\"Brief & QA\",\"description\":\"Initial information gathering and technical questions.\",\"icon\":\"i-lucide-clipboard-list\"},{\"title\":\"Introduction Call\",\"description\":\"Meet the team, understand the business context.\",\"icon\":\"i-lucide-phone\"},{\"title\":\"Discovery Workshop\",\"description\":\"Deep dive into requirements, constraints, and architecture.\",\"icon\":\"i-lucide-search\"},{\"title\":\"Ballpark Proposal\",\"description\":\"Scope, timeline, and cost estimate.\",\"icon\":\"i-lucide-file-text\"},{\"title\":\"Decision\",\"description\":\"Agreement on scope, technology, and team composition.\",\"icon\":\"i-lucide-check-circle\"}]",[842,87438,87439],{},"A consensus was reached that developing a new frontend from the ground up and integrating it with the existing backend would yield the most favorable outcomes within the specified budget and timeline. FastAPI was identified as the ideal technology for the backend migration.",[4937,87441],{},[863,87443,27663],{"id":52066},[842,87445,87446],{},"MusicTech Lab recommended migrating from the old technology (Flask) to FastAPI and refactoring the existing functionality. The objective was to optimize and reduce the technical debt of the application.",[1045,87448,87450,87454,87459,87463],{"className":87449},[1048,1049,1765,1051,1052],[1054,87451],{"description":87452,"icon":9656,"title":87453},"Migrated the backend to FastAPI for better performance, async support, and modern Python practices.","Flask → FastAPI Migration",[1054,87455],{"description":87456,"icon":87457,"title":87458},"Designed reusable frontend components as building blocks for new features, maintaining visual consistency.","i-lucide-component","Reusable Component Library",[1054,87460],{"description":87461,"icon":5507,"title":87462},"Built a library of design patterns, rules, and UX guidelines to prevent inconsistencies at scale.","Design System",[1054,87464],{"description":87465,"icon":3920,"title":87466},"Separated backend and frontend, significantly improving application performance.","Performance Optimization",[4937,87468],{},[863,87470,87472],{"id":87471},"the-team","The Team",[1045,87474,87476,87480,87484,87489],{"className":87475},[1048,50574,50605,1051,1052],[1054,87477],{"description":87478,"icon":72248,"title":87479},"Vue.js Developer","Mateusz",[1054,87481],{"description":87482,"icon":6395,"title":87483},"Python Developer","Michał",[1054,87485],{"description":87486,"icon":87487,"title":87488},"UX/UI Senior Designer","i-lucide-figma","Jakub",[1054,87490],{"description":87491,"icon":5340,"title":87492},"Delivery Manager","Mariusz",[4937,87494],{},[863,87496,87498],{"id":87497},"testimonials","Testimonials",[41054,87500,87501],{},[842,87502,87503],{},"Panther project has no middle management. Flat structure enables us to take the responsibility, make confident decisions, and contribute to the client's product development.",[842,87505,87506,87509],{},[996,87507,87508],{},"Mateusz Bryzik"," — Frontend Developer",[41054,87511,87512],{},[842,87513,87514],{},"The team consisted of three experienced and highly motivated developers, we had frontend and backend developers, and we were working closely with UI/UX designers.",[842,87516,87517,87520],{},[996,87518,87519],{},"Nils Streitbürger"," — Managing Director, Panther Solutions GmbH",[842,87522,87523],{},[846,87524,72338],{"href":87525,"rel":87526},"https://clutch.co/profile/musictech-lab?page=2#review-1892385",[850],[4937,87528],{},[863,87530,87532],{"id":87531},"the-result","The Result",[842,87534,87535],{},"The comprehensive integration of research, design, and development led to a powerful platform capable of automating 100% real-time price recommendations using AI.",[1045,87537,87539,87543,87547,87551,87555,87559],{"className":87538},[1048,1049,1050,1051,1052],[1054,87540],{"description":87541,"icon":13608,"title":87542},"Fully integrated, always up-to-date online market prices for each item.","Online Price Labeller",[1054,87544],{"description":87545,"icon":3181,"title":87546},"Ideal online price suggestions for optimal margin exploitation.","Optimal Price Proposals",[1054,87548],{"description":87549,"icon":11614,"title":87550},"Sales forecasts, price elasticities, competitive pricing, item performance, and inventory considerations.","AI-Based Forecasts",[1054,87552],{"description":87553,"icon":85133,"title":87554},"Intelligent markdown strategies for lifecycle and inventory management.","Price Markdown Optimization",[1054,87556],{"description":87557,"icon":1774,"title":87558},"Improved yield and liquidity through data-driven pricing decisions.","Yield & Liquidity",[1054,87560],{"description":87561,"icon":13562,"title":87562},"Reduced manual work and operational costs through automation.","Process Simplification",[1572,87564,87565],{},[842,87566,87567],{},"By separating the backend and rewriting the frontend from scratch, we significantly improved Panther Pricing's performance while maintaining a reasonable scope and cost. The modular frontend allows adding new functionalities without compromising visual consistency.",[4937,87569],{},[863,87571,51026],{"id":51025},[1045,87573,87575],{"className":87574},[13033,50238,50239,1052],[50241,87576],{"color":50243,"label":87577,"target":50245,"to":87525,"variant":50246},"Clutch Review",{"title":728,"searchDepth":729,"depth":729,"links":87579},[87580,87581,87582,87583,87584,87585,87586,87587,87588,87589],{"id":87363,"depth":729,"text":87364},{"id":65385,"depth":729,"text":65386},{"id":66001,"depth":729,"text":66002},{"id":52034,"depth":729,"text":52035},{"id":87432,"depth":729,"text":87433},{"id":52066,"depth":729,"text":27663},{"id":87471,"depth":729,"text":87472},{"id":87497,"depth":729,"text":87498},{"id":87531,"depth":729,"text":87532},{"id":51025,"depth":729,"text":51026},{"name":87591},"Panther Pricing","2022-12-01T00:00:00.000Z","How we migrated Panther Pricing from Flask to FastAPI and built an ML-based web platform for optimizing retail pricing in brick-and-mortar stores.",{"src":87595},"/images/cdn-migrated/bravelab_experience_panther_pricing.webp",{"enabled":738,"items":87597},[87598,87600,87602,87604],{"text":87599,"icon":87068},"Migrated backend from Flask to FastAPI, cutting technical debt significantly.",{"text":87601,"icon":8500},"AI-powered platform automates 100% of real-time price recommendations for retailers.",{"text":87603,"icon":5507},"Reusable Vue.js component library and design system ensure visual consistency at scale.",{"text":87605,"icon":4845},"Flat team structure with no middle management enabled fast, confident decisions.",{},{"title":42,"description":87593},[4990,14100],"jQ5H6_-FZbtPGgirEd1YUlAgP8_ZfUPCaBL2mA0tOfI",{"id":87611,"title":50,"authors":723,"badge":87612,"body":87613,"category":4990,"client":87775,"date":87592,"description":87777,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":87778,"keyTakeaways":87780,"meta":87788,"navigation":738,"path":51,"seo":87789,"status":723,"stem":52,"tags":87790,"teaser":723,"__hash__":87791},"posts/blog/case-study/talent-alpha-hr-platform-case-study.md",{"label":5,"color":50099},{"type":725,"value":87614,"toc":87763},[87615,87617,87620,87622,87653,87655,87658,87660,87663,87666,87669,87672,87675,87678,87681,87685,87687,87691,87694,87698,87703,87708,87710,87715,87721,87727,87731,87733,87735,87737,87739,87741,87744,87747,87749],[842,87616,40867],{},[842,87618,87619],{},"We provided an HR startup with frontend developers to augment their in-house team with their internal projects based on Angular and React.js frameworks.",[863,87621,65386],{"id":65385},[958,87623,87624,87630,87635,87641,87647],{},[961,87625,87626,87629],{},[996,87627,87628],{},"TypeScript"," - Type-safe JavaScript development",[961,87631,87632,87634],{},[996,87633,65403],{}," - Database",[961,87636,87637,87640],{},[996,87638,87639],{},"Angular"," - Frontend framework",[961,87642,87643,87646],{},[996,87644,87645],{},"React"," - Frontend library",[961,87648,87649,87652],{},[996,87650,87651],{},"Angular Material"," - UI component library",[863,87654,52035],{"id":52034},[842,87656,87657],{},"Talent Alpha team set themselves an ambitious goal to prepare a certain amount of new features before the conference. With the deadline approaching, they realised they needed to extend their front-end development team to complete the work on time.",[863,87659,27663],{"id":52066},[842,87661,87662],{},"The client was looking for a partner who could provide resources to augment their development team.",[842,87664,87665],{},"We supported the client's frontend team. Each of our developers was onboarded through the regular recruitment process. Our developers were a part of the frontend team, ready to support any projects the client was working on. Both of the companies are based in Krakow so we could meet when necessary, however the work was mostly remote.",[842,87667,87668],{},"The client set the goal to develop a certain number of features to be available for the October 2019 conference. Thanks to our support, the client was able to deliver those features on time.",[842,87670,87671],{},"Talent Alpha is an expert in finding the right people for the job. It was no different when it came to finding extra help with their own project.",[842,87673,87674],{},"Our developer - Mateusz - had to go through the entire recruitment process. No corners were cut. But even with such a thorough screening, the onboarding process was swift.",[842,87676,87677],{},"Mateusz quickly became part of the Talent Alpha internal front-end team, working hand in hand with other developers to deliver the desired features on time.",[842,87679,87680],{},"All the work had been done remotely, which required excellent communication skills from both parties.",[863,87682,87684],{"id":87683},"the-need","The Need",[842,87686,87662],{},[863,87688,87690],{"id":87689},"solution-details","Solution Details",[842,87692,87693],{},"We hired Bravelab.io to support our frontend team. Each of their developers were onboarded through our regular recruitment process, wherein they were given tasks to complete, and then interviewed if they were successful.",[863,87695,87697],{"id":87696},"team-testimonial","Team Testimonial",[41054,87699,87700],{},[842,87701,87702],{},"I had a great experience working with Talent Alpha team. They were very communicative, responsive and flexible throughout the project. I would happily work with them again in the future.",[842,87704,87705,87707],{},[996,87706,87508],{}," - Frontend Developer",[863,87709,66034],{"id":66033},[41054,87711,87712],{},[842,87713,87714],{},"Without the help of their team, we wouldn't have been able to accomplish the goals we'd set. I work with them on a daily basis, and I'm very happy with the quality of their work and all aspects of the partnership.",[842,87716,87717,87720],{},[996,87718,87719],{},"Marcin Zbijowski"," - Technical Lead Talent Alpha",[842,87722,87723],{},[846,87724,72338],{"href":87725,"rel":87726},"https://clutch.co/profile/musictech-lab#review-1318374",[850],[863,87728,87730],{"id":87729},"the-work","The Work",[842,87732,87671],{},[842,87734,87674],{},[842,87736,87677],{},[842,87738,87680],{},[863,87740,52145],{"id":52144},[842,87742,87743],{},"We are proud to say that with our help, Talent Alpha was able to deliver on their promise. All the scheduled and desired features were ready before the conference.",[842,87745,87746],{},"Additionally, our cooperation with Talent Alpha is ongoing. Bravelab is their go-to development company when they need extra help with their projects.",[863,87748,52192],{"id":52191},[958,87750,87751,87757],{},[961,87752,87753,87756],{},[996,87754,87755],{},"Project Time:"," 8 months",[961,87758,87759,87762],{},[996,87760,87761],{},"Team:"," Mateusz - Frontend Developer (Angular/React)",{"title":728,"searchDepth":729,"depth":729,"links":87764},[87765,87766,87767,87768,87769,87770,87771,87772,87773,87774],{"id":65385,"depth":729,"text":65386},{"id":52034,"depth":729,"text":52035},{"id":52066,"depth":729,"text":27663},{"id":87683,"depth":729,"text":87684},{"id":87689,"depth":729,"text":87690},{"id":87696,"depth":729,"text":87697},{"id":66033,"depth":729,"text":66034},{"id":87729,"depth":729,"text":87730},{"id":52144,"depth":729,"text":52145},{"id":52191,"depth":729,"text":52192},{"name":87776},"Talent Alpha","How we augmented an HR startup's in-house team with frontend developers for Angular and React.js projects, delivering features on a tight deadline.",{"src":87779},"/images/cdn-migrated/bravelab_experience_talent_alpha.webp",{"enabled":738,"items":87781},[87782,87784,87786],{"text":87783,"icon":4845},"Frontend developers augmented the client's team to hit a conference deadline.",{"text":87785,"icon":10530},"All scheduled features delivered on time after 8 months of collaboration.",{"text":87787,"icon":1067},"Fully remote work with developers onboarded through the client's own recruitment process.",{},{"title":50,"description":87777},[4990,18784],"NNZ9PcFELu4C4xu5-7muXp7eOv5bqXc1QWbcshEcOeM",{"id":87793,"title":200,"authors":87794,"badge":723,"body":87798,"category":731,"client":723,"date":87935,"description":87936,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":87937,"keyTakeaways":87939,"meta":87947,"navigation":738,"path":201,"seo":87948,"status":723,"stem":202,"tags":87949,"teaser":723,"__hash__":87950},"posts/blog/music-data/why-we-decided-to-use-wavesurfer.md",[87795],{"name":87508,"avatar":87796},{"src":87797},"/images/people/mateusz-bryzik.webp",{"type":725,"value":87799,"toc":87927},[87800,87804,87807,87824,87828,87831,87835,87838,87843,87847,87855,87871,87874,87878,87889,87901,87904,87907,87912,87915],[863,87801,87803],{"id":87802},"the-main-challenge","The main challenge",[842,87805,87806],{},"In one of our projects, we faced the business requirement to create a custom audio player — nothing complex, with several controls: play, pause, previous and next track, and changing volume. The challenge was the player should also be responsible for drawing the audio spectrum.",[1045,87808,87810,87814,87819],{"className":87809},[1048,1049,1050,1051,1052],[1054,87811],{"description":87812,"icon":87813,"title":65745},"Play, pause, previous/next track, volume control.","i-lucide-play",[1054,87815],{"description":87816,"icon":87817,"title":87818},"Visual representation of the audio signal drawn in real time.","i-lucide-audio-waveform","Waveform Spectrum",[1054,87820],{"description":87821,"icon":87822,"title":87823},"Must match our existing UI — no generic player widgets.","i-lucide-paintbrush","Custom Design",[863,87825,87827],{"id":87826},"available-solutions","Available solutions",[842,87829,87830],{},"Building a basic audio player with the browser's Audio API is straightforward. Drawing a waveform spectrum is where it gets complex. There are algorithms available on the web, but adjusting them to specific designs and interactions is time-consuming. We decided to look for a ready-made library.",[863,87832,87834],{"id":87833},"does-a-vuejs-audio-player-library-exist","Does a Vue.js Audio Player library exist?",[842,87836,87837],{},"At MusicTech Lab, our standard frontend stack is Vue.js. Our first idea was to find a Vue.js audio library. Unfortunately, none of the available options matched our needs — some could play tracks and draw a spectrum, but customising them to our designs was impossible. Time was running out, and we had only a few weeks to close this topic.",[1901,87839,87840],{},[842,87841,87842],{},"We evaluated several Vue.js audio libraries, but none offered the combination of waveform rendering and design flexibility we needed. Most were either too opinionated in their UI or lacked spectrum visualisation entirely.",[863,87844,87846],{"id":87845},"our-solution-wavesurferjs","Our solution: WaveSurfer.js",[842,87848,87849,87850,87854],{},"Finally, we found ",[846,87851,26253],{"href":87852,"rel":87853},"https://wavesurfer-js.org/",[850]," — a customisable audio spectrum visualisation library based on the Audio API and HTML5 Canvas. With over ten years of development, it was a proven candidate.",[1045,87856,87858,87862,87866],{"className":87857},[1048,1049,1050,1051,1052],[1054,87859],{"description":87860,"icon":87861,"title":65745},"Play, pause, switch tracks, volume — all supported out of the box.","i-lucide-circle-play",[1054,87863],{"description":87864,"icon":5507,"title":87865},"Fully customisable spectrum — colours, height, progress bar, container size.","Waveform Rendering",[1054,87867],{"description":87868,"icon":87869,"title":87870},"Extend functionality with plugins like Regions, Timeline, and Minimap.","i-lucide-puzzle","Plugin System",[842,87872,87873],{},"We quickly configured the waveform container — height, width, colours for the wave and progress — and it matched our designs. A few lines of configuration replaced hundreds of lines of custom code.",[863,87875,87877],{"id":87876},"the-regions-plugin","The Regions Plugin",[842,87879,87880,87881,87884,87885,87888],{},"Our business requirement was to let users play ",[996,87882,87883],{},"hooks"," — specific parts of a track like a chorus or guitar riff, highlighted on the spectrum. The ",[996,87886,87887],{},"Regions"," plugin was exactly what we needed.",[1045,87890,87892,87897],{"className":87891},[1048,1049,1765,1051,1052],[1054,87893],{"description":87894,"icon":87895,"title":87896},"Creates highlighted regions on the waveform that can be resized, moved, and played in a loop.","i-lucide-scissors","Visual Markers",[1054,87898],{"description":87899,"icon":64120,"title":87900},"Users select a hook — chorus, riff, intro — and the Regions plugin handles looped playback of that segment.","Loop Playback",[842,87902,87903],{},"The hook menu above the spectrum is a custom component, but the hook highlighting and playback is fully powered by the Regions plugin.",[863,87905,87906],{"id":1473},"Result",[1032,87908,87909],{},[842,87910,87911],{},"WaveSurfer.js saved us significant development time. The configuration is simple, the library is stable, and you can achieve what you need quickly. The only downside is the documentation — it could use more examples for advanced methods.",[842,87913,87914],{},"We recommend this library to anyone building a similar audio component.",[1045,87916,87918,87920,87924],{"className":87917},[13033,50238,50239,1052],[50241,87919],{"color":50243,"label":26253,"target":50245,"to":87852,"variant":50246},[50241,87921],{"color":50249,"label":87922,"target":50245,"to":87923,"variant":50246},"Plugins","https://wavesurfer-js.org/plugins/",[50241,87925],{"color":50249,"label":15320,"target":50245,"to":87926,"variant":50246},"https://github.com/katspaugh/wavesurfer.js",{"title":728,"searchDepth":729,"depth":729,"links":87928},[87929,87930,87931,87932,87933,87934],{"id":87802,"depth":729,"text":87803},{"id":87826,"depth":729,"text":87827},{"id":87833,"depth":729,"text":87834},{"id":87845,"depth":729,"text":87846},{"id":87876,"depth":729,"text":87877},{"id":1473,"depth":729,"text":87906},"2022-07-05T00:00:00.000Z","Creating a simple, custom audio player with main functionality is relatively easy. Are you sure? Let's see how we tackled this issue",{"src":87938},"/images/blog/musictechlab_blog_why-we-decided-to-use-wavesurfer.webp",{"enabled":738,"items":87940},[87941,87943,87945],{"text":87942,"icon":5365},"WaveSurfer.js replaced hundreds of lines of custom waveform code with a few config lines.",{"text":87944,"icon":40852},"The Regions plugin handles hook highlighting and looped playback out of the box.",{"text":87946,"icon":7560},"No Vue.js audio library offered both waveform rendering and design flexibility.",{},{"title":200,"description":87936},[26062,18784,27927],"X1tV_GLxKKa2kXdZt19ajJ2Me10bpOEFQWfvwqnY8j4",{"id":87952,"title":474,"authors":87953,"badge":723,"body":87956,"category":756,"client":723,"date":88014,"description":88015,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":88016,"keyTakeaways":88018,"meta":88028,"navigation":738,"path":475,"seo":88029,"status":723,"stem":476,"tags":88030,"teaser":723,"__hash__":88031},"posts/blog/software-development/hasura-in-action.md",[87954],{"name":86504,"avatar":87955},{"src":86506},{"type":725,"value":87957,"toc":88009},[87958,87960,87964,87967,87970,87979,87983,87986,87994,87998,88001,88004,88007],[842,87959,40867],{},[863,87961,87963],{"id":87962},"what-is-hasura","What is Hasura",[842,87965,87966],{},"Hasura is an application which gives you an instant GraphQL/REST API based on your database schema. As of writing it supports Postgresql, SQL Server and Big Query databases but MySQL, Oracle and Elastic are also in the pipeline. You can use it for a rapid prototyping as well as production deployments as it can be scaled horizontally and has very good performance characteristics. If you are wondering - it is written in Haskel, and it’s very likely it will run much faster than your regular Django app.",[842,87968,87969],{},"Over the past few years, it has enjoyed a lot of adoption in the industry ie. Walmart, Atlassian, Netlify to name the few.",[842,87971,87972,87973,87978],{},"At Bravelab we believe it will play an important role as one of the tools improving development and time to market speeds. In fact Hasura team estimates that adopting it cuts down on development time by 50-80% according to ",[846,87974,87977],{"href":87975,"rel":87976},"https://hasura.io/case-studies/",[850],"their studies."," Anything that’s saving you that much time is worth a try, right ?",[863,87980,87982],{"id":87981},"how-do-you-develop-an-application-with-hasura-aka-where-do-i-put-business-logic","How do you develop an application with Hasura - aka where do I put business logic ?",[842,87984,87985],{},"This is usually the first question - after all we are used to writing a lot of code. Hasura’s philosophy sits nicely in line with my personal view of how we should be writing an applications:",[958,87987,87988,87991],{},[961,87989,87990],{},"Assume you can do it in SQL unless proven otherwise. Your logic should be as close to the data as possible. This simple assumption eliminates a host of problems, and those who have experienced them know them very well. No round trips, no 1 selects, no stale data, no cashing problems, no date/timezone aware code to write - the list goes on. This comes down to using SQL functions, procedures, views, materialised views, triggers, different column types (like json, array), extensions and indexes. So by utilizing functionality that is already build in your database Hasura can take full advantage of that by exposing procedures or functions as fields in the GraphQL schema.",[961,87992,87993],{},"Delegate standalone functionalities to separate libraries, services, cloud functions. When you are dealing with oauth2, registration, data import, payment systems etc. these are usually present in every app. So you can write this functionality once and reuse. If you expose it on the backend or lambda function you can use Hasura Actions/Remote Schemas to proxy the requests to these services.",[863,87995,87997],{"id":87996},"example-of-a-simple-backend-integration","Example of a simple backend integration",[842,87999,88000],{},"On the django side let’s write a simple django view which will return json data.",[842,88002,88003],{},"Deploying Hasura usually consists of two stages. Firstly, we need to export our local configuration to yaml files. Once you’ve crafted the exact setup you can use gui or command line command for exporting ie.",[842,88005,88006],{},"At Bravelab, we really care about delivering the best solution in the quickest possible manner. Over the years, we have been crafting API’s and backends by hand but with the arrival of modern no-code tooling like Hasura it’s becoming more and more apparent we want to be solving problems and not reinventing the wheel. We have chosen this solution because with a bit of configuration it takes away all these repetitive tasks from developers and let them focus on more challenging aspects of the project. Also for our clients it’s a great tool for integration with perhaps legacy systems and payment gateways. Overall our experience of using it has been great so far!",[842,88008,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":88010},[88011,88012,88013],{"id":87962,"depth":729,"text":87963},{"id":87981,"depth":729,"text":87982},{"id":87996,"depth":729,"text":87997},"2022-06-29T00:00:00.000Z","Learn how to use Hasura with Django to get instant GraphQL/REST APIs from your database schema. Covers setup, business logic, and backend integration.",{"src":88017},"/images/blog/musictechlab_blog_hasura-in-action.webp",{"enabled":738,"items":88019},[88020,88022,88024,88026],{"text":88021,"icon":2939},"Hasura gives instant GraphQL/REST APIs from your database schema with zero code.",{"text":88023,"icon":3271},"Adopting Hasura cuts development time by 50-80% according to their case studies.",{"text":88025,"icon":2895},"Business logic should live as close to the data as possible, ideally in SQL.",{"text":88027,"icon":1067},"Used by Walmart, Atlassian, and Netlify in production environments.",{},{"title":474,"description":88015},[18784],"bjvfeVa5pEp7C8BTKDCaX-WkxV-rtFb1xjAXm-sJjPM",{"id":88033,"title":654,"authors":88034,"badge":723,"body":88039,"category":756,"client":723,"date":88182,"description":88183,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":88184,"keyTakeaways":88186,"meta":88194,"navigation":738,"path":655,"seo":88195,"status":723,"stem":656,"tags":88196,"teaser":723,"__hash__":88197},"posts/blog/software-development/top-6-apps-made-with-flutter.md",[88035],{"name":88036,"avatar":88037},"Victor Temitope Awogbemila",{"src":88038},"/images/people/victor-temitope-awogbemila.webp",{"type":725,"value":88040,"toc":88179},[88041,88043,88050,88053,88056,88060,88065,88073,88076,88081,88089,88091,88094,88103,88110,88113,88118,88126,88129,88135,88137,88142,88150,88153,88158,88166,88169,88177],[842,88042,40867],{},[842,88044,88045,88049],{},[1027,88046],{"alt":88047,"src":88048},"Top apps built with Flutter framework","/images/blog/musictechlab_blog_top-6-apps-made-with-flutter-1.webp","Flutter is currently one of the most popular frameworks for cross-platform UI development, offering a single codebase usable for multiple devices. Programming in Flutter is fast, cost-efficient, and easy to learn, making it a go-to choice for not only mobile developers, but desktop and web dev teams as well.",[842,88051,88052],{},"If you ever wondered whether it’s worth it to use Flutter when building an app, all it takes is a single look at all the successful apps created with Flutter - from social networking, to shopping apps, to entertainment and business management.",[842,88054,88055],{},"Still not convinced?",[863,88057,88059],{"id":88058},"here-are-the-top-6-apps-developed-using-flutter-that-achieved-massive-financial-success","Here are the top 6 apps developed using Flutter that achieved massive financial success:",[842,88061,88062],{},[996,88063,88064],{},"Xianyu",[842,88066,88067,88068,88072],{},"Alibaba is one of the biggest e-commerce companies in the world, with hundreds of millions of users and a variety of services, including a diverse marketplace for second-hand goods. One of the flagship apps from Alibaba, ",[846,88069,88064],{"href":88070,"rel":88071},"https://2.taobao.com/",[850],", has been created with Flutter for iOS and Android to provide a gorgeous UI and flawless performance.",[842,88074,88075],{},"According to the developers at Alibaba Group, Flutter opened up new possibilities of UI and UX design, creating low-latency, smooth UI. It also saved the developers tons of time thanks to the cross-platform functionalities, and the single codebase made maintenance much less of a hassle.",[842,88077,88078],{},[996,88079,88080],{},"Google Ads",[842,88082,88083,88084,88088],{},"Since Flutter was created by Google as an open-source project, a lot of the company’s applications were made using the Flutter SDK. One of the most popular of Google’s apps is the ",[846,88085,88080],{"href":88086,"rel":88087},"https://ads.google.com/home/",[850],"app for smartphones, giving campaign managers quick access to ad campaigns directly from their mobile devices.",[842,88090,52316],{},[842,88092,88093],{},"Thanks to Flutter, Google Ads is highly usable not only on desktop, but also on mobile, giving remote workers constant access to campaign settings and statistics from almost anywhere in the world. The mobile Google Ads app is intuitive and aesthetically pleasing, all thanks to the Flutter framework.",[842,88095,88096,88100],{},[1027,88097],{"alt":88098,"src":88099},"AppTree enterprise app built with Flutter","/images/blog/musictechlab_blog_top-6-apps-made-with-flutter-3.webp",[996,88101,88102],{},"AppTree",[842,88104,88105,88109],{},[846,88106,88102],{"href":88107,"rel":88108},"https://www.apptreerevolution.com/",[850]," is a popular enterprise app platform built using Flutter, highly scalable and offering extensive capabilities even when offline. The app can additionally be connected with other enterprise apps, as well as used to create high-quality workflows.",[842,88111,88112],{},"The app features a variety of enterprise assistance features, including handling large amounts of data, implementation of security policies, managing inspections, time cards, dispatching, and much more.",[842,88114,88115],{},[996,88116,88117],{},"KlasterMe",[842,88119,88120,88121,88125],{},"If you’re looking for an innovative social networking app with a crisp UI, ",[846,88122,88117],{"href":88123,"rel":88124},"https://itsallwidgets.com/klasterme",[850]," is definitely one of the top contenders. The app is available on iOS and Android and allows users to create their own pages that display different types of content like articles and images.",[842,88127,88128],{},"one in no time at all. The developers of KlasterMe themselves stated they received massive support from the Flutter community, with developers from all over the world happy to answer questions and help resolve problems.",[842,88130,88131,52316],{},[1027,88132],{"alt":88133,"src":88134},"KlasterMe social networking app built with Flutter","/images/blog/musictechlab_blog_top-6-apps-made-with-flutter-2.webp",[842,88136,52316],{},[842,88138,88139],{},[996,88140,88141],{},"Reflectly",[842,88143,88144,88145,88149],{},"Flutter framework turned useful for the health & fitness industry as well. ",[846,88146,88141],{"href":88147,"rel":88148},"https://reflectly.app/",[850]," is a popular personal mental health app, with advanced AI capabilities that constantly helps users create journals of what’s on their mind, as well as possible solutions to their problems.",[842,88151,88152],{},"According to Reflectly devs, they first tried building their app in React Native, but encountered a lot of problems with cross-platform compatibility. This led to delays and inefficiencies, but switching to Flutter immediately solved that problem and allowed the company to keep a fast and consistent release schedule.",[842,88154,88155],{},[996,88156,88157],{},"Google Stadia",[842,88159,88160,88161,88165],{},"While video game streaming is still a relatively niche service, ",[846,88162,88157],{"href":88163,"rel":88164},"https://stadia.google.com/",[850]," is turning out to be maybe the most promising one. Whether you’re playing on PC or mobile, Stadia allows you to stream video games directly to your device, without the high hardware requirements that often come with new releases.",[842,88167,88168],{},"Google Stadia developers are closely in cooperation with the team at Flutter, who helped Stadia create its mobile app. Thanks to Flutter, Google Stadia looks great and performs even better.",[842,88170,88171,88172,38428],{},"Why not have your app be the next one on the list? Our developers at Bravelab are experienced in working with the Flutter SDK, creating highly-interactive and visually pleasing cross-platform applications. Take your app to the next level with the help of Flutter and ",[846,88173,88176],{"href":88174,"rel":88175},"https://bravelab.io/contact",[850],"Bravelab",[842,88178,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":88180},[88181],{"id":88058,"depth":729,"text":88059},"2022-06-22T00:00:00.000Z","See the most popular apps made in Google Flutter. What needs do they answer and why have they gained popularity and business success? Get inspired by them!",{"src":88185},"/images/blog/musictechlab_blog_top-6-apps-made-with-flutter.webp",{"enabled":738,"items":88187},[88188,88190,88192],{"text":88189,"icon":4855},"Alibaba's Xianyu app uses Flutter for iOS and Android, serving hundreds of millions of users.",{"text":88191,"icon":5365},"Reflectly switched from React Native to Flutter, immediately solving cross-platform compatibility issues.",{"text":88193,"icon":2939},"Flutter's single codebase cuts development time and simplifies maintenance for multi-platform apps.",{},{"title":654,"description":88183},[18784],"Tjv0XcoceqVQhxs-XEdsBRWXMAu6Mu2XLVrEkFwrp8o",{"id":88199,"title":466,"authors":88200,"badge":723,"body":88203,"category":756,"client":723,"date":88307,"description":88308,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":88309,"keyTakeaways":88311,"meta":88321,"navigation":738,"path":467,"seo":88322,"status":723,"stem":468,"tags":88323,"teaser":723,"__hash__":88324},"posts/blog/software-development/flutter-strategy-for-2022-analyzing-the-new-flutter-roadmap.md",[88201],{"name":88036,"avatar":88202},{"src":88038},{"type":725,"value":88204,"toc":88302},[88205,88207,88210,88213,88216,88223,88226,88229,88233,88242,88245,88248,88255,88258,88261,88265,88268,88277,88280,88284,88292,88300],[842,88206,40867],{},[842,88208,88209],{},"Flutter is a popular open-source UI framework for cross-platform development, created by Google and officially launched in 2018. With many additions that improve development efficiency and a diverse community of developers from all over the world contributing to the project, Flutter is one of the top SDKs to look out for in 2022.",[842,88211,88212],{},"A while ago, Google released their strategy and roadmap for 2022, showcasing what to expect in the upcoming updates. If you’re using Flutter or are planning to start learning how to use it, this analysis will help you prepare for the new changes.",[842,88214,88215],{},"Let’s see what’s in store for Flutter developers in 2022.",[842,88217,88218,88222],{},[1027,88219],{"alt":88220,"src":88221},"Flutter 2022 strategy and current state analysis","/images/blog/musictechlab_blog_flutter-strategy-2022-1.webp","## Analyzing the current Flutter state",[842,88224,88225],{},"While Flutter began as a Software Development Kit for mobile apps exclusively, being available only on Android and iOS, it is now available on a variety of platforms, including Linux, macOS, Windows, as well as Google Fuchsia. It’s currently the most popular cross-platform mobile toolkit, with over 500,000 applications created using the SDK.",[842,88227,88228],{},"Flutter isn’t only Google’s pet project - massive numbers of developers from all over the world contribute to the project every day, and not all of them are on Google’s payroll. The open-source nature of Flutter ensures it isn’t merely Google’s corporate product, but a passion project of dedicated developers. In fact, many other big corporations have also contributed, including Microsoft, Sony, Alibaba, Toyota, and more.",[863,88230,88232],{"id":88231},"the-move-towards-a-more-dev-friendly-sdk","The move towards a more dev-friendly SDK",[842,88234,88235,88236,88241],{},"In 2022, Flutter developers can expect many new quality-of-life additions, as the SDKs primary focus becomes excellent developer experience. As described in the ",[846,88237,88240],{"href":88238,"rel":88239},"https://docs.google.com/document/d/e/2PACX-1vTI9X2XHN_IY8wDO4epQSD1CkRT8WDxf2CEExp5Ef4Id206UOMopkYqU73FvAnnYG6NAecNSDo9TaEO/pub",[850],"Official Flutter Strategy for 2022",", only the best developer experience will convince creators to use other tools than those default to each platform, and that’s exactly the plan for 2022.",[842,88243,88244],{},"Not that developer experience wasn’t a focus before, but with more resources and skilled coders than ever, the team is redoubling their efforts on making Flutter the most developer-friendly and efficient platform across multiple platforms.",[842,88246,88247],{},"Speaking about cross-platform functionalities, let’s see what the developers are working on for multi-platform applications:",[842,88249,88250,88254],{},[1027,88251],{"alt":88252,"src":88253},"Flutter multi-platform quality roadmap for 2022","/images/blog/musictechlab_blog_flutter-strategy-2022-2.webp","## A new standard of multi-platform quality",[842,88256,88257],{},"It’s clear that Flutter is not limited to iOS and Android anymore - still, a vast majority of Flutter users are mobile developers, as the SDK offers the best functionality on these platforms. Back in 2019, Google’s developers promised to deliver equal-level experience on desktop and browsers as well - and that’s exactly the team’s focus this year.",[842,88259,88260],{},"Flutter developers should expect many functions that were previously only available on mobile to appear on desktop and browsers as well in 2022. The SDK is already highly compatible with many browser-based technologies like Canvas and WebGPU, and the API will continue to be expanded in the upcoming months. Dart was also said to receive WebAssembly support somewhere in the future.",[863,88262,88264],{"id":88263},"additional-changes-to-flutters-framework","Additional changes to Flutter’s framework",[842,88266,88267],{},"The new focus on cross-platform functionality for desktop and browser apps doesn’t mean mobile developers won’t receive any goodies this year. The team continues to allocate a large portion of their resources to both iOS and Android development, carefully polishing the fundamentals of the SDK.",[842,88269,88270,88271,88276],{},"Cybersecurity is always a concern in modern times, with cyber crime rates rising every year and many companies thought credible before falling victim to cyber attacks. Cybercriminals target more and more open-source projects, prompting the Flutter team to increase their efforts to remove vulnerabilities and make the whole SDK safer and more reliable, eventually supposed to fulfill the ",[846,88272,88275],{"href":88273,"rel":88274},"https://slsa.dev/spec/v0.1/",[850],"SLSA"," level 4 requirements.",[842,88278,88279],{},"In 2021, Flutter developers were mainly focused on improving the fundamental aspects of the Flutter framework, which they intend to continue in 2022. Flutter’s Material library will be updated to support Material 3, and the team already confirmed they aim to improve the text editing experience on all platforms. Multi-window rendering from a single Isolate was also announced.",[863,88281,88283],{"id":88282},"final-remarks-the-future-of-flutter","Final remarks - the future of Flutter",[842,88285,88286,88287,861],{},"2022 will bring some exciting changes and improvements to the Flutter SDK, further incentivizing developers from all over the world to choose the open-source project over default tools. For more specifics, check out the ",[846,88288,88291],{"href":88289,"rel":88290},"https://github.com/flutter/flutter/wiki/Roadmap",[850],"official Flutter 2022 Roadmap",[842,88293,88294,88295,88299],{},"For more exciting news, keep following our blog at ",[846,88296,88298],{"href":88297},"/blog","Bravelab.io"," - we are a Software House that specializes in Python and JavaScript-based custom-built Web Platforms, made with Vue.js, Python and using Sanity.io and Saleor.io frameworks.",[842,88301,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":88303},[88304,88305,88306],{"id":88231,"depth":729,"text":88232},{"id":88263,"depth":729,"text":88264},{"id":88282,"depth":729,"text":88283},"2022-06-20T00:00:00.000Z","An analysis of Flutter's 2022 roadmap. Google's plans to make Flutter more user-friendly and efficient for cross-platform development.",{"src":88310},"/images/blog/musictechlab_blog_flutter-strategy-for-2022-analyzing-the-new-flutter-roadmap.webp",{"enabled":738,"items":88312},[88313,88315,88317,88319],{"text":88314,"icon":4855},"Over 500,000 apps have been built with Flutter across mobile, desktop, and web.",{"text":88316,"icon":85476},"Flutter 2022 roadmap focused on equal desktop/web quality alongside mobile.",{"text":88318,"icon":7495},"Material 3 support, multi-window rendering, and SLSA level 4 security were planned.",{"text":88320,"icon":4845},"Contributors include Microsoft, Sony, Alibaba, and Toyota, not just Google.",{},{"title":466,"description":88308},[18784],"y52fr2WQCsitZe1buzY06mcgaCCdKpnfUHMZLa_qyLo",{"id":88326,"title":698,"authors":88327,"badge":723,"body":88332,"category":756,"client":723,"date":88549,"description":88550,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":88551,"keyTakeaways":88553,"meta":88561,"navigation":738,"path":699,"seo":88562,"status":723,"stem":700,"tags":88563,"teaser":723,"__hash__":88564},"posts/blog/software-development/why-we-use-sanity-io.md",[88328],{"name":88329,"avatar":88330},"Oleksiy Kalashnyk",{"src":88331},"/images/people/oleksiy-kalashnyk.webp",{"type":725,"value":88333,"toc":88539},[88334,88336,88346,88349,88363,88366,88373,88386,88394,88446,88451,88454,88465,88472,88486,88489,88492,88497,88510,88513,88516,88537],[842,88335,40867],{},[863,88337,88339,88340],{"id":88338},"what-is-sanityio","**What is **",[846,88341,88344],{"href":88342,"rel":88343},"https://sanity.io/",[850],[996,88345,72251],{},[842,88347,88348],{},"Sanity is a host server for structured content with a real-time API, it provides us with two main products:",[958,88350,88351,88357],{},[961,88352,88353,88356],{},[996,88354,88355],{},"Content Lake"," is a real-time database with a powerful but simple query language GROQ (Query Cheat Sheet) and globally distributed CDN",[961,88358,88359,88362],{},[996,88360,88361],{},"Sanity Studio"," is a flexible open source content editing environment written in React, which can be customized and personalized using basic JavaScript",[842,88364,88365],{},"You can use them separately as well as in combination with each other.",[842,88367,88368,88369],{},"The image is from the official website ",[846,88370,88372],{"href":88342,"rel":88371},[850],"sanity.io",[863,88374,88376],{"id":88375},"what-are-the-main-advantages-and-disadvantages-of-sanityio",[996,88377,88378,88379,38405],{},"What are the main advantages and disadvantages of ",[996,88380,88381],{},[846,88382,88384],{"href":88342,"rel":88383},[850],[996,88385,72251],{},[842,88387,88388,88393],{},[846,88389,88391],{"href":88342,"rel":88390},[850],[996,88392,72251],{}," has a number of advantages over its alternatives, such as GraphCMS, Storyblok, Contentful, etc.The main ones are:",[958,88395,88396,88399,88402,88405,88408,88411,88414,88417,88420,88425,88428,88431,88434,88437,88440],{},[961,88397,88398],{},"Automatic project setup in one line of code (simple Sanity CLI)",[961,88400,88401],{},"Excellent documentation and a huge community",[961,88403,88404],{},"Massive library of official and custom plugins",[961,88406,88407],{},"Excellent integration with all popular programming languages and frameworks (with ready-made templates)",[961,88409,88410],{},"User-friendly and intuitive content management board. Custom React UI Components.",[961,88412,88413],{},"Sanity comes with a powerful image pipeline that does image metadata . All served from a global CDN. Extraction on upload transformations in front-end usage",[961,88415,88416],{},"GraphQL support",[961,88418,88419],{},"Version control",[961,88421,88422,1133],{},[996,88423,88424],{},"Free forever",[961,88426,88427],{},"500k API CDN Requests per month",[961,88429,88430],{},"100k API Requests per month",[961,88432,88433],{},"5GB Assets",[961,88435,88436],{},"Unlimited admin users",[961,88438,88439],{},"3 Non-admin users",[961,88441,88442,88445],{},[996,88443,88444],{},"GROQ"," (Graph-Relational Object Queries) is a declarative language designed to query collections of JSON documents. At Sanity, the goal was expressive filtering, combining multiple documents into one response and shaping the response according to the client application.",[842,88447,88368,88448],{},[846,88449,88372],{"href":88342,"rel":88450},[850],[842,88452,88453],{},"Of course there are also disadvantages:",[958,88455,88456,88459,88462],{},[961,88457,88458],{},"There is no multi language in the main assembly (only with the help of plugins)",[961,88460,88461],{},"Attachment to a specific platform (Sanity) when developing data for a website",[961,88463,88464],{},"With a large number of requests and responses, you need to switch to a paid tariff (but it is not expensive)",[842,88466,88467,88468,88471],{},"The conclusion is simple - ",[996,88469,88470],{},"Sanity"," is the best choice.",[863,88473,88475],{"id":88474},"why-sanityio-is-not-a-cms-like-a-wordpress",[996,88476,88477,88478,88485],{},"Why ",[996,88479,88480],{},[846,88481,88483],{"href":88342,"rel":88482},[850],[996,88484,72251],{}," is not a CMS like a Wordpress",[842,88487,88488],{},"CMS ( Content Management System) is a site content management system. In professional jargon, CMS is also called the \"site engine\". For example, Drupal, Joomla, WordPress and others.",[842,88490,88491],{},"CMS is, in fact, a set of hundreds of plugins and libraries in one place. So that the user can independently perform basic manipulations with appearance and data. And for more advanced features, you need to buy new extensions (often by subscription), which sometimes conflict with each other. Such an approach nowadays is a relic of the past and with each new project more and more companies stop using them.",[842,88493,88494,88496],{},[996,88495,88470],{},", in turn, preaches a different approach to information processing. We only specify the data schemas that we need. Based on this scheme, Sanity creates a user interface for creating content. This gives us the opportunity to fully concentrate on business logic and not engage in unnecessary optimizations. Sanity will do everything instead of us. It remains for us to get the data in our application using the API.",[863,88498,88500],{"id":88499},"why-do-we-choose-sanityio",[996,88501,88502,88503,38405],{},"Why do we choose ",[996,88504,88505],{},[846,88506,88508],{"href":88342,"rel":88507},[850],[996,88509,72251],{},[842,88511,88512],{},"High performance, easy scaling, code base reuse and ease of use - allows us to provide many of our customers with a simple and at the same time high-quality user interface for data management. We really like the simple integration of Sanity with Netlify, as well as, thanks to additional datasets, easy setup among the development. Sanity also has access control for users and easy connection to the API from any platform (web applications, mobile applications, and even desktop).",[842,88514,88515],{},"In one of the last projects, thanks to the official (free) add-ons, we implemented multilingualism in a couple of clicks. Many other systems often have problems with that.",[842,88517,88518,88524,88525,88530,88531,88536],{},[846,88519,88522],{"href":88520,"rel":88521},"https://bravelab.io/",[850],[996,88523,88298],{}," has been repeatedly convinced that ",[846,88526,88528],{"href":88342,"rel":88527},[850],[996,88529,72251],{}," is the best option not only for large commercial applications but also for small projects. We see how ",[846,88532,88534],{"href":88342,"rel":88533},[850],[996,88535,72251],{}," develops in move with the times, giving us more opportunities with each update.",[842,88538,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":88540},[88541,88543,88545,88547],{"id":88338,"depth":729,"text":88542},"**What is **Sanity.io",{"id":88375,"depth":729,"text":88544},"What are the main advantages and disadvantages of Sanity.io?",{"id":88474,"depth":729,"text":88546},"Why Sanity.io is not a CMS like a Wordpress",{"id":88499,"depth":729,"text":88548},"Why do we choose Sanity.io?","2022-05-31T00:00:00.000Z","Sanity.io has a number of advantages over its alternatives, such as GraphCMS, Storyblok, Contentful, etc. Find out what they are...",{"src":88552},"/images/blog/musictechlab_blog_why-we-use-sanity-io.webp",{"enabled":738,"items":88554},[88555,88557,88559],{"text":88556,"icon":1774},"Sanity's free tier includes 500k CDN requests, 100k API requests, and unlimited admin users.",{"text":88558,"icon":2895},"GROQ query language enables expressive filtering and response shaping for JSON document collections.",{"text":88560,"icon":5507},"Sanity Studio is a customizable React-based editor, unlike rigid WordPress-style CMS platforms.",{},{"title":698,"description":88550},[18784,15279],"71dt8m2SIZk1loTXxfxKZXeCoi4kXavfDElRSdET1_g",{"id":88566,"title":674,"authors":88567,"badge":723,"body":88572,"category":756,"client":723,"date":88690,"description":88691,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":88692,"keyTakeaways":88694,"meta":88702,"navigation":738,"path":675,"seo":88703,"status":723,"stem":676,"tags":88704,"teaser":723,"__hash__":88705},"posts/blog/software-development/what-is-flutter-and-why-is-it-worth-considering.md",[88568],{"name":88569,"avatar":88570},"Krystyna Zabiega",{"src":88571},"/images/people/krystyna-zabiega.webp",{"type":725,"value":88573,"toc":88683},[88574,88580,88583,88586,88589,88593,88596,88599,88602,88606,88612,88615,88618,88621,88625,88631,88634,88637,88641,88644,88671,88675,88678],[842,88575,88576],{},[1027,88577],{"alt":88578,"src":88579},"Flutter cross-platform development overview","/images/blog/musictechlab_blog_what-is-flutter-2.webp",[842,88581,88582],{},"Unless you've been sleeping under a rock for the last two years, you must have heard about Flutter if you're even remotely interested in mobile app development. Since 2018, Flutter has become the most popular cross-platform SDK for mobile developers, open-source and constantly updated, with over 500,000 applications developed using Flutter.",[842,88584,88585],{},"In 2022, Flutter isn't only a UI SDK for mobile platforms anymore — it's been also made compatible with desktop platforms, including Windows, macOS, and Linux. It's also designed for web development, making it a great all-around choice for developers.",[842,88587,88588],{},"What's so great about Flutter and why might it be very worth it to learn Flutter development? Let's take a look.",[863,88590,88592],{"id":88591},"flutter-a-cross-platform-development-toolkit","Flutter — a cross-platform development toolkit",[842,88594,88595],{},"Flutter first saw the light of day in 2015, and remained in pre-release state until the end of 2018. Even though Flutter was created by Google, it's been released as an open-source mobile framework, developed by thousands of independent developers from all around the world.",[842,88597,88598],{},"The first major upside of Flutter was the ability to create native mobile applications for iOS and Android using only a single codebase. This made creating cross-platform applications more viable, as it offered a vast improvement when compared to the amount of work needed to create two separate versions of the app.",[842,88600,88601],{},"The foundations of Flutter are built on C++, with an extensive framework based on the Dart programming language. The SDK is built on highly-customizable widgets with extensive behavior controls using efficient code. This makes Flutter extremely approachable, lowering the entry bar for app developers and making coding simple, fun, and cost-effective.",[863,88603,88605],{"id":88604},"flutter-development-for-mobile-applications","Flutter development for mobile applications",[842,88607,88608],{},[1027,88609],{"alt":88610,"src":88611},"Flutter mobile app development","/images/blog/musictechlab_blog_what-is-flutter-3.webp",[842,88613,88614],{},"Even though nowadays Flutter has received full support for multiple platforms, it began as a mobile-exclusive UI software development kit. To this day, a large portion of the team's resources are dedicated to improving Flutter for mobile devices.",[842,88616,88617],{},"As mentioned before, cross-platform development for iOS and Android is one of the primary uses of Flutter. Flutter builds highly-interactive apps with a device-friendly ecosystem that supports a range of hardware and services.",[842,88619,88620],{},"If you're afraid of Google's corporate agenda, they are by far not the only contributor to Flutter — the SDK is co-created with support from other companies like Microsoft and Sony, as well as countless developers from around the globe contributing to the project in their own time.",[863,88622,88624],{"id":88623},"flutters-desktop-and-web-support","Flutter's desktop and web support",[842,88626,88627],{},[1027,88628],{"alt":88629,"src":88630},"Flutter desktop and web platform support","/images/blog/musictechlab_blog_what-is-flutter-1.webp",[842,88632,88633],{},"As Flutter grew, it also started supporting the development of highly-efficient UI solutions for web and desktop using an advanced framework. New projects can be built with web support in mind, and existing projects can quickly add web and desktop support when needed using a single codebase.",[842,88635,88636],{},"The team continues to expand desktop and web capabilities, working toward equal-level experience across all platforms. This can potentially create a one-codebase framework for cross-platform development on mobile, desktop, and web, cutting development time by a large margin.",[863,88638,88640],{"id":88639},"the-benefits-of-using-flutter","The benefits of using Flutter",[842,88642,88643],{},"When looking for an efficient environment for building cross-platform applications, Flutter is definitely one of the best choices available. For both small and large companies developing multi-platform software, Flutter presents a range of benefits:",[1045,88645,88647,88651,88655,88659,88663,88667],{"className":88646},[1048,1049,1765,1051,1052],[1054,88648],{"description":88649,"icon":8737,"title":88650},"An easy-to-learn architecture built on composable, customizable widgets.","Widget-Based Framework",[1054,88652],{"description":88653,"icon":3266,"title":88654},"Maximize productivity with one codebase for mobile, desktop, and web.","Single Codebase",[1054,88656],{"description":88657,"icon":2939,"title":88658},"See changes instantly without losing app state — faster iteration cycles.","Hot Reload",[1054,88660],{"description":88661,"icon":50617,"title":88662},"Compiles to native ARM code for near-native performance on every platform.","High Performance",[1054,88664],{"description":88665,"icon":1067,"title":88666},"Compatible with iOS, Android, Windows, macOS, Linux, and web.","Multi-Platform",[1054,88668],{"description":88669,"icon":4845,"title":88670},"Backed by Google and a vast community of independent contributors worldwide.","Open Source Community",[863,88672,88674],{"id":88673},"is-it-worth-creating-an-app-in-flutter","Is it worth creating an app in Flutter?",[842,88676,88677],{},"Looking at the current market and Google's continued investment, there is no doubt that Flutter is and will continue to be a top SDK for cross-platform app development — not only for mobile, but other platforms as well.",[1032,88679,88680],{},[842,88681,88682],{},"At MusicTech Lab, Flutter is one of our core technologies. We use it for products like Ambistream, BeatBuddy, and MemoSonic — all built with a single codebase across iOS and Android.",{"title":728,"searchDepth":729,"depth":729,"links":88684},[88685,88686,88687,88688,88689],{"id":88591,"depth":729,"text":88592},{"id":88604,"depth":729,"text":88605},{"id":88623,"depth":729,"text":88624},{"id":88639,"depth":729,"text":88640},{"id":88673,"depth":729,"text":88674},"2022-05-05T00:00:00.000Z","Why Flutter is worth considering for cross-platform development. Explore its widget-based architecture, Dart foundation, and mobile, web, and desktop support.",{"src":88693},"/images/blog/musictechlab_blog_what-is-flutter-and-why-is-it-worth-considering.webp",{"enabled":738,"items":88695},[88696,88698,88700],{"text":88697,"icon":4855},"Flutter supports iOS, Android, Windows, macOS, Linux, and web from a single codebase.",{"text":88699,"icon":3920},"Over 500,000 apps have been built with Flutter since its 2018 stable release.",{"text":88701,"icon":2939},"Hot reload lets developers see UI changes instantly without losing application state.",{},{"title":674,"description":88691},[18784],"KkRMIBAkQ0gnyFHxjVisvJt5saSl49Nj0TFDaMWCnFg",{"id":88707,"title":486,"authors":88708,"badge":723,"body":88713,"category":756,"client":723,"date":88864,"description":88865,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":88866,"keyTakeaways":88868,"meta":88878,"navigation":738,"path":487,"seo":88879,"status":723,"stem":488,"tags":88880,"teaser":723,"__hash__":88881},"posts/blog/software-development/how-important-is-good-ux-ui-design.md",[88709],{"name":88710,"avatar":88711},"Adam Mośko",{"src":88712},"/images/people/adam-mosko.webp",{"type":725,"value":88714,"toc":88858},[88715,88717,88724,88727,88733,88743,88746,88752,88766,88769,88772,88775,88781,88784,88787,88807,88817,88824,88835,88842,88846,88853,88856],[842,88716,40867],{},[842,88718,88719,88723],{},[1027,88720],{"alt":88721,"src":88722},"UX and UI design importance for digital products","/images/blog/musictechlab_blog_ux-ui-design-1.webp","What is the measure of good user experience? Few users will actually let you know they had a good experience using your Digital Product. What they will act upon, however, is poor user experience - if the app was clunky, inconsistent, unintuitive, or had poor performance, most users will simply swap to a better alternative.",[842,88725,88726],{},"To avoid that, it’s essential to ensure your app has an easy-to-read UI and effective UX design. But what exactly are these two buzzwords, and how important are they for creating a successful app?",[863,88728,88730],{"id":88729},"what-is-a-user-interface-ui-and-how-to-design-it",[996,88731,88732],{},"What is a User Interface (UI) and how to design it?",[842,88734,88735,88738,88739,88742],{},[996,88736,88737],{},"User Interface",", often shortened to ",[996,88740,88741],{},"UI"," for simplicity, is the graphical layer of any application or website that the user sees and interacts with directly. From images to sliders to buttons, User Interface includes all sorts of visual and interactive elements presented to the end user.",[842,88744,88745],{},"As you might imagine, without proper planning and design, the UI of your app might end up an incoherent mess that will be difficult to interact with by your users. While you and your team understand the underlying framework that’s underneath the interface and instinctively know how to navigate it, your users do not have that privilege. If the UI isn’t simple to navigate and attractive to look at, your users might end up looking for a better alternative.",[863,88747,88749],{"id":88748},"what-is-user-experience-ux-and-how-is-it-different-from-ui",[996,88750,88751],{},"What is User Experience (UX) and how is it different from UI?",[842,88753,88754,88758,88761,88762,88765],{},[1027,88755],{"alt":88756,"src":88757},"Difference between UX and UI design","/images/blog/musictechlab_blog_ux-ui-design-2.webp",[996,88759,88760],{},"User Experience"," (or simply ",[996,88763,88764],{},"UX",") on the other hand, deals with the bigger picture - not only how good the software looks or how well it can be navigated, but everything pertaining to achieving a positive user experience.",[842,88767,88768],{},"UX design is an extremely complex task, requiring the designers to reflect on virtually every aspect of the app to make all the components work together in harmony. From acquiring the product, to actually using it, to troubleshooting and maintenance - all of these stages need to be designed not only with usability in mind, but also efficiency, ease of use, and fun.",[842,88770,88771],{},"The difference between UX and UI is simple - while the UI design is concerned only with visual and interactive elements, UX design aims to improve the entire experience of using your product or service by creating a logical user flow.",[842,88773,88774],{},"However, since the software industry is constantly evolving and changing, don’t get too attached to these definitions - they are prone to change and be redefined. Instead of thinking how UI and UX are different, think about how they actually complement each other.",[863,88776,88778],{"id":88777},"why-is-ux-and-ui-design-so-important",[996,88779,88780],{},"Why is UX and UI design so important?",[842,88782,88783],{},"Making your app as usable and user-friendly as possible isn’t merely an act of goodwill - proper UX and UI design is directly linked to increasing your sales and encouraging business growth. While your app might be revolutionary in terms of coding, it won’t do well without people who actually use it regularly, especially since it’s extremely difficult to offer an entirely unique digital product or service nowadays.",[842,88785,88786],{},"That’s when UX and UI come into play - to convince the potential users that your app is the best choice out of all the alternatives, grabbing their attention and proving to them that this is exactly what they were looking for. With so many alternatives within a hand’s reach, you have an extremely short timespan to win the user’s heart, and UX and UI are the perfect tools for doing exactly that.",[842,88788,88789,88790,5660,88795,88798,88799,88802,88803,88806],{},"According to ",[846,88791,88794],{"href":88792,"rel":88793},"https://www.forrester.com/report/The-Six-Steps-For-Justifying-Better-UX/RES117708",[850],"Forrester",[996,88796,88797],{},"each dollar invested in UX design results in a return of $100",", and a flawless UX could ",[996,88800,88801],{},"raise conversion rates up to 400%"," . Recent studies also show that 88% of users are less likely to return to a website after a bad experience, and 90% stopped using an app because of performance issues. This is especially relevant for mobile users, who are ",[996,88804,88805],{},"5 times as likely"," to abandon their tasks if the app or site isn’t optimized well.",[842,88808,88809,88813,88814],{},[1027,88810],{"alt":88811,"src":88812},"Essential components of UX/UI design","/images/blog/musictechlab_blog_ux-ui-design-3.webp","## ",[996,88815,88816],{},"The essential components of UX/UI design",[842,88818,88819,88820,88823],{},"While it is important for your app to look visually pleasing, it should above all else be ",[996,88821,88822],{},"intuitive to use",". One of the main objectives of UX design is to maximally shorten the amount of time needed for the customer to start using your app proficiently. The user flow of your app should reflect real-world interactivity or already well-established standards, like swiping left to move to the next page or having a cogwheel represent settings.",[842,88825,88826,88827,88830,88831,88834],{},"Your app’s design should also be ",[996,88828,88829],{},"consistent",". If you choose to represent a certain functionality in a specific way, make sure it is represented as such in every area of your app. Don’t confuse your user by changing the rules in different areas of your app - ",[996,88832,88833],{},"simplicity and coherence"," should be your goal.",[842,88836,88837,88838,88841],{},"The specific ",[996,88839,88840],{},"platform"," you’re designing your app for also has an impact on UX/UI design. You don’t want your PC users to have a poorly ported mobile app on their device, as the two are fundamentally different in how people use them. Many systems and devices offer specific recommendations for developers to make it simpler to implement a common look and feel in a certain environment, like Microsoft Fluent Design System for Windows 10 or Material Design Guidelines for Android.",[863,88843,88844],{"id":18680},[996,88845,18681],{},[842,88847,88848,88849,88852],{},"As you can see, UX and UI are extremely important aspects of any app or website, dictating conversion rates and directly increasing profits. Poorly optimized websites cost retailers over ",[996,88850,88851],{},"$2 billion in lost sales each year."," Don’t follow their example - take care of proper UX/UI design.",[842,88854,88855],{},"Here at Bravelab, we offer professional UX & UI design and optimization services, making every effort to make your app visually pleasing and user-friendly. Find out how we can help you and make an appointment using the form below.",[842,88857,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":88859},[88860,88861,88862,88863],{"id":88729,"depth":729,"text":88732},{"id":88748,"depth":729,"text":88751},{"id":88777,"depth":729,"text":88780},{"id":18680,"depth":729,"text":18681},"2022-05-04T00:00:00.000Z","UX and UI are critical aspects of any application, determining conversion rates and profits. Learn how good design prevents users from switching to competitors.",{"src":88867},"/images/blog/musictechlab_blog_how-important-is-good-ux-ui-design.webp",{"enabled":738,"items":88869},[88870,88872,88874,88876],{"text":88871,"icon":5504},"Every $1 invested in UX returns $100, and flawless UX can raise conversions by 400%.",{"text":88873,"icon":3847},"88% of users won't return after a bad experience; 90% leave over performance issues.",{"text":88875,"icon":4855},"Mobile users are 5x more likely to abandon tasks on unoptimized apps.",{"text":88877,"icon":3920},"Poorly optimized websites cost retailers over $2 billion in lost sales annually.",{},{"title":486,"description":88865},[18784],"FPlhKHWB_-rw66NX1upwjbIs8CwAlrlDB88CnBpzINc",{"id":88883,"title":326,"authors":88884,"badge":723,"body":88887,"category":756,"client":723,"date":89106,"description":89107,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":89108,"keyTakeaways":89110,"meta":89120,"navigation":738,"path":327,"seo":89121,"status":723,"stem":328,"tags":89122,"teaser":723,"__hash__":89123},"posts/blog/software-development/9-reasons-why-the-saleor-io-platform-is-the-best-choice-for-your-ecommerce-website.md",[88885],{"name":88036,"avatar":88886},{"src":88038},{"type":725,"value":88888,"toc":89093},[88889,88891,88893,88900,88903,88915,88923,88932,88935,88943,88946,88960,88963,88969,88972,88978,88981,88984,88994,89002,89005,89011,89014,89020,89023,89029,89032,89035,89041,89044,89050,89063,89069,89072,89081,89084],[842,88890,40867],{},[842,88892,52316],{},[842,88894,88895,88899],{},[1027,88896],{"alt":88897,"src":88898},"Saleor headless eCommerce platform overview","/images/blog/musictechlab_blog_saleor-reasons-2.webp","In an ideal world, Shopify, WooCommerce, or other popular eCommerce platforms would be enough to satisfy every eCommerce business’s needs. However, in the real world (not always ideal by the way), the most popular eCommerce platforms don’t cater to the needs of every business, shopper, and developer.",[842,88901,88902],{},"ECommerce merchants create customer-centric strategies that aim to give visitors the best shopping experience at their stores. Therefore, their primary concern is simplifying and speeding up the purchasing journey with every technology they can afford. This presented a time-consuming problem for developers in the past. However, newer eCommerce platforms work the developer-friendly angle and opt for a headless framework, making the developer’s job much easier. And in our experience, when your developer is happy, your product is all the better.",[842,88904,88905,88909,88910],{},[846,88906,79491],{"href":88907,"rel":88908},"https://saleor.io/",[850]," is one of the fastest-rising developer-friendly headless eCommerce platforms out there. ",[964,88911,88912],{},[996,88913,88914],{},"This article examines what separates the Saleor platform from other eCommerce platforms and why it might be the best option for your eCommerce website.",[863,88916,88918],{"id":88917},"introducing-saleor",[964,88919,88920],{},[996,88921,88922],{},"Introducing Saleor",[842,88924,88925,88926,88931],{},"Saleor is a headless, GraphQL-first, open-source eCommerce platform. Its modern, efficient stack is based on a Python 3 and Django 2 framework. It uses fast, flexible, and reliable React APIs written in Typescript and Apollo GraphQL. Since version 2.0 was released, the platform’s ",[846,88927,88930],{"href":88928,"rel":88929},"https://saleor.io/blog/saleor-release-graphqlfirst-headless-ecommerce-102/",[850],"REST solutions were replaced with GraphQL",", which promises cleaner and faster APIs. In simpler terms, Saleor allows you to create beautiful online stores with all the features of an excellent eCommerce platform. In addition, its headless GraphQL-first architecture delivers faster, flexible experiences for your customers.",[842,88933,88934],{},"Here, let’s break down what a headless eCommerce platform means and why the Saleor platform will significantly benefit you.",[863,88936,88938],{"id":88937},"what-is-a-headless-ecommerce-platform",[964,88939,88940],{},[996,88941,88942],{},"What Is a Headless eCommerce Platform?",[842,88944,88945],{},"A headless eCommerce platform (also API-driven) is an architecture where back-end/server-side elements are entirely decoupled from the front-end experience. A Headless eCommerce platform presents greater flexibility in front-end design and API choice.",[842,88947,88948,88949,1589,88954,88959],{},"A benefit of employing headless architecture is the freedom to create different versions of your front-end for your various channels. Since the front-end is completely decoupled from the back-end, the possibility of an issue with a specific parameter or function interfering with another parameter is eliminated, thereby increasing the stability of your front-end. A headless architecture also allows for more excellent integration opportunities, allowing you to harness the power of platforms like ",[846,88950,88953],{"href":88951,"rel":88952},"https://cloud.google.com/",[850],"Google Cloud",[846,88955,88958],{"href":88956,"rel":88957},"https://aws.amazon.com/",[850],"AWS",". Finally, headless platforms are faster and more scalable.",[842,88961,88962],{},"Now, these beautiful features are peculiar to most headless platforms. So what sets Saleor apart from the others? So sit tight and explore a few ways Saleor trumps other eCommerce platforms.",[863,88964,88966],{"id":88965},"why-choose-saleor",[996,88967,88968],{},"Why Choose Saleor?",[842,88970,88971],{},"The Saleor platform provides the following advantages over most other eCommerce platforms:",[1074,88973,88975],{"id":88974},"_1-omnichannel-accessibility",[996,88976,88977],{},"1. Omnichannel Accessibility",[842,88979,88980],{},"ECommerce websites built on Saleor work seamlessly on all devices and screen sizes, thanks to Saleor’s headless architecture. Your website also works as a progressive web app (PWA) on mobile devices.",[842,88982,88983],{},"Since you have total control over your front-end layer, you may choose to use a pre-made template, a JavaScript SDK, or you can build a project from scratch in the language and platform of your choice. All power rests in your hands.",[842,88985,88986,88990,88991],{},[1027,88987],{"alt":88988,"src":88989},"Saleor PWA and omnichannel features","/images/blog/musictechlab_blog_saleor-reasons-3.webp","### ",[996,88992,88993],{},"2. PWA (Progressive Web Apps)",[842,88995,88996,88997,861],{},"Saleor eCommerce websites act both as traditional online stores and PWAs. PWAs allow your eCommerce website to take all the advantages of a mobile application without the need for customers to install anything. It also means you can save cost on building a separate mobile application which many customers will end up uninstalling anyway. Not us; it’s what the numbers say; ",[846,88998,89001],{"href":88999,"rel":89000},"https://techcrunch.com/2016/05/31/nearly-1-in-4-people-abandon-mobile-apps-after-only-one-use/",[850],"62% of users uninstall apps after only 11 uses, and 23% open mobile apps just once",[842,89003,89004],{},"Want to know something even more incredible? You can deliver excellent shopping experiences with your PWA even if your customers are offline without an internet connection.",[1074,89006,89008],{"id":89007},"_3-seo-efficiency",[996,89009,89010],{},"3. SEO efficiency",[842,89012,89013],{},"Saleor eCommerce websites have static front-end layers, which Google favors as they’re fast, light, and easy to scan for robots. So, you can set out creating SEO-optimized taglines, meta-data, content for products, collections, and categories, confident that your eCommerce website has better chances with Google’s algorithms.",[1074,89015,89017],{"id":89016},"_4-speedy-customer-experience",[996,89018,89019],{},"4. Speedy Customer Experience",[842,89021,89022],{},"Saleor’s headless and GraphQL-first architecture makes for shorter page load times and less lag in the customer experience. This is especially important as fast page speeds play an important role in keeping visitors on your website. It’s also one of the most important Google ranking factors.",[1074,89024,89026],{"id":89025},"_5-flexible-products-configuration",[996,89027,89028],{},"5. Flexible Products Configuration",[842,89030,89031],{},"Your inventory is more flexible as it can contain both digital and physical products with different attributes as bundles and collections or individually. All of these are easy to configure with Saleor.",[842,89033,89034],{},"You can also manage multiple warehouses with Saleor. Keep track of the stock in each warehouse, edit numerous products at once, and see which products or warehouses are out of stock from your Saleor eCommerce dashboard.",[1074,89036,89038],{"id":89037},"_6-globalization",[996,89039,89040],{},"6. Globalization",[842,89042,89043],{},"The Saleor platform currently supports more than 30 languages. Thus you can translate sections of your entire eCommerce website into other languages. Saleor also has geolocation capabilities that come handy when looking to serve a global audience.",[1074,89045,89047],{"id":89046},"_7-diverse-integration-options",[996,89048,89049],{},"7. Diverse Integration Options",[842,89051,89052,89053,1589,89058,89062],{},"You can Integrate with Google Analytics to track your website’s traffic and more. Integrate with popular payment channels such as ",[846,89054,89057],{"href":89055,"rel":89056},"https://www.paypal.com/",[850],"PayPal",[846,89059,52120],{"href":89060,"rel":89061},"https://stripe.com/",[850],". Integrate admin sign-in with Google or Facebook to restrict unwanted access to your admin dashboard. The options are limitless.",[1074,89064,89066],{"id":89065},"_8-data-driven-marketing-and-sales",[996,89067,89068],{},"8. Data-driven Marketing and Sales",[842,89070,89071],{},"Saleor has machine learning capabilities to help you make data-informed adjustments to your page setup or checkout process and generate more revenue by upselling. Also, track your website’s performance by reviewing sales figures in real-time or past-time. A/B testing with Saleor is also data-driven. As a result, it provides better results to optimize your customer experience.",[842,89073,89074,88990,89078],{},[1027,89075],{"alt":89076,"src":89077},"Saleor eCommerce platform safety and security","/images/blog/musictechlab_blog_saleor-reasons-1.webp",[996,89079,89080],{},"9. Safety",[842,89082,89083],{},"Since the front-end layer that your customers see is completely separated from your server-side, customers’ details and other sensitive information are safe.",[842,89085,89086,89087,89092],{},"Saleor has a vast and fast-growing community with over 180 active contributors. ",[846,89088,89091],{"href":89089,"rel":89090},"https://techcrunch.com/2021/03/18/saleor/amp/",[850],"In addition, the company received $2.5 million in funding from Berlin-based company Cherry Ventures"," in March 2021. These developments indicate the bright future ahead for the Saleor eCommerce platform. We can expect future updates to improve present core competencies and possess more excellent opportunities for integration - these promise an efficient platform for your business and better experiences for your customers.",{"title":728,"searchDepth":729,"depth":729,"links":89094},[89095,89096,89097],{"id":88917,"depth":729,"text":88922},{"id":88937,"depth":729,"text":88942},{"id":88965,"depth":729,"text":88968,"children":89098},[89099,89100,89101,89102,89103,89104,89105],{"id":88974,"depth":1112,"text":88977},{"id":89007,"depth":1112,"text":89010},{"id":89016,"depth":1112,"text":89019},{"id":89025,"depth":1112,"text":89028},{"id":89037,"depth":1112,"text":89040},{"id":89046,"depth":1112,"text":89049},{"id":89065,"depth":1112,"text":89068},"2022-04-25T00:00:00.000Z","Discover why the Saleor headless eCommerce platform is making waves. Nine key reasons it delivers incredible online shopping experiences.",{"src":89109},"/images/blog/musictechlab_blog_9-reasons-why-the-saleor-io-platform-is-the-best-choice-for-your-ecommerce-website.webp",{"enabled":738,"items":89111},[89112,89114,89116,89118],{"text":89113,"icon":5365},"Saleor is a headless, GraphQL-first, open-source eCommerce platform built on Python and Django.",{"text":89115,"icon":4855},"PWA support means no separate mobile app needed; 62% of users uninstall apps after 11 uses.",{"text":89117,"icon":1769},"Headless architecture decouples frontend from backend for faster page loads and better SEO.",{"text":89119,"icon":1067},"Supports 30+ languages and multi-warehouse inventory management out of the box.",{},{"title":326,"description":89107},[52276,18784],"swTTmPuXG-A4F1H_zM25qf1h1E25q__dqwp3KdTbkI8",{"id":89125,"title":494,"authors":89126,"badge":723,"body":89129,"category":756,"client":723,"date":89286,"description":89287,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":89288,"keyTakeaways":723,"meta":89290,"navigation":738,"path":495,"seo":89291,"status":723,"stem":496,"tags":89292,"teaser":723,"__hash__":89293},"posts/blog/software-development/how-to-become-a-vue-js-developer-and-whether-paid-trials-in-it-work-out.md",[89127],{"name":88329,"avatar":89128},{"src":88331},{"type":725,"value":89130,"toc":89276},[89131,89133,89136,89140,89143,89145,89148,89151,89155,89166,89169,89174,89178,89192,89195,89202,89204,89207,89230,89236,89241,89244,89247,89255,89259,89266,89274],[842,89132,40867],{},[842,89134,89135],{},"Our journey together began not so long ago. And I would like to share with you exactly how this happened.",[863,89137,89139],{"id":89138},"i-am-a-junior-frontend-developer-vue-at-bravelab","I am a Junior Frontend Developer (Vue) at Bravelab.",[842,89141,89142],{},"For three years I practiced programming as a hobby in my free time. This was my answer to the question “What’s next?”. I didn’t see myself in physical work at an older age. Having a technical degree and confidence that it's never too late to learn, I wrote my first “Hello world!” program at the age of 25.",[863,89144],{"id":728},[842,89146,89147],{},"I had no commercial or team experience. But I was given a chance at Bravelab.",[842,89149,89150],{},"After the recruitment process, I was offered a paid two week trial period - that already was very pleasant. On the first day of the trial period, I was introduced to the “Friend in Brave” system. What does this actually mean? Each member of the team is actually your colleague and you can easily turn to him with any question and ask for support. With Jira and Kanban tables, we can easily track and plan our actions. That was also shown in practice at once.",[1074,89152,89154],{"id":89153},"three-more-people-are-involved-in-the-development-of-the-project-i-am-working-on","Three more people are involved in the development of the project I am working on:",[958,89156,89157,89160,89163],{},[961,89158,89159],{},"Team leader",[961,89161,89162],{},"A more experienced developer",[961,89164,89165],{},"UI/UX designer",[842,89167,89168],{},"For the first five days, the senior developer and I did tasks together, sometimes even in pair coding or in Google Meet conferences. That allowed me to quickly enter the project.",[842,89170,89171],{},[996,89172,89173],{},"I find that our work as a team is very effective.",[863,89175,89177],{"id":89176},"the-process-of-working-on-an-it-trial-is-very-simple","The process of working on an IT trial is very simple:",[958,89179,89180,89183,89186,89189],{},[961,89181,89182],{},"Every morning together with the Team lead we discuss what we have already done and what needs to be done according to the plan",[961,89184,89185],{},"After, in direct contact with the designer we see what it should look like. If we have any questions or suggestions, we review them together.",[961,89187,89188],{},"Together with a senior programmer colleague, we discuss how to do the task from a technical point of view.",[961,89190,89191],{},"Then we do it and see the result.",[842,89193,89194],{},"Our task is to make a high-quality product both visually and in the code component.",[842,89196,89197],{},[964,89198,89199],{},[996,89200,89201],{},"It is very easy for me as a beginner as there is no pressure and no rush.",[863,89203],{"id":76849},[842,89205,89206],{},"In reference to the technical stack:",[958,89208,89209,89212,89215,89218,89221,89224,89227],{},[961,89210,89211],{},"Sanity.io - as a CMS system",[961,89213,89214],{},"Vue.js javascript framework",[961,89216,89217],{},"Tailwind CSS - style framework",[961,89219,89220],{},"Gridsome for project build and server rendering",[961,89222,89223],{},"GraphQL - data requests",[961,89225,89226],{},"Netlify for project deployment",[961,89228,89229],{},"Figma/Zeplin - prototyping and design of components and pages",[863,89231,89233],{"id":89232},"now-more-about-each-of-them",[996,89234,89235],{},"Now more about each of them.",[842,89237,89238,89240],{},[996,89239,72251],{}," turned out to be a very nice and flexible Headless CMS. I really liked their documentation, it allowed me to quickly get into the project. The atomic schema structure makes it very easy to scale an application. The ability to use different datasets gives field for testing and it is convenient for local deployments. It makes teamwork easier.",[842,89242,89243],{},"**Vue.js **is a simple and highly efficient JavaScript framework. With its help, the implementation accelerates significantly. Convenient component decomposition.",[842,89245,89246],{},"**Tailwind CSS **is one of the most popular CSS frameworks in the world. Its customization and built-in features save a lot of time. GraphQL, an optimized API query language, is very easy for beginners.",[842,89248,89249,1589,89252,89254],{},[996,89250,89251],{},"Gridsome",[996,89253,72291],{}," are highly efficient tools for building and deploying a project. Gridsome, thanks to its modularity, is very suitable for both small and large projects. Netlify, as a leader in cloud computing, needs no introduction.",[863,89256,89258],{"id":89257},"the-summary-for-the-paid-trail-is-simple","The summary for the paid trail is simple.",[842,89260,89261],{},[964,89262,89263],{},[996,89264,89265],{},"Here at Bravelab, everyone can make their dreams come true.",[842,89267,89268,89269,89273],{},"At 28, I officially became a programmer - and I am very happy about it. I want to sincerely thank all the people who helped me in this process. Bohdan, for pointing out and recommending Bravelab, Lena and Dina for the great recruitment and onboarding process, and last but not least Adam, Jurek, and Krystyna for everyday, consistent work on the ",[846,89270,89272],{"href":88520,"rel":89271},[850],"bravelab.io"," website project.",[842,89275,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":89277},[89278,89279,89282,89283,89284,89285],{"id":89138,"depth":729,"text":89139},{"id":728,"depth":729,"text":728,"children":89280},[89281],{"id":89153,"depth":1112,"text":89154},{"id":89176,"depth":729,"text":89177},{"id":76849,"depth":729,"text":728},{"id":89232,"depth":729,"text":89235},{"id":89257,"depth":729,"text":89258},"2022-03-30T00:00:00.000Z","A junior developer shares their experience joining a software team through a paid two-week trial period and how it helped both sides evaluate the fit.",{"src":89289},"/images/blog/musictechlab_blog_how-to-become-a-vue-js-developer-and-whether-paid-trials-in-it-work-out.webp",{},{"title":494,"description":89287},[18784,74615],"G56bQJF91-wujlgXo9i6SlNmnR1aUNEbquBNp-7Dik0",{"id":89295,"title":330,"authors":89296,"badge":723,"body":89299,"category":756,"client":723,"date":89390,"description":89391,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":89392,"keyTakeaways":89394,"meta":89402,"navigation":738,"path":331,"seo":89403,"status":723,"stem":332,"tags":89404,"teaser":723,"__hash__":89405},"posts/blog/software-development/a-look-at-bravelab-ios-clutch-2021-year-in-review.md",[89297],{"name":88569,"avatar":89298},{"src":88571},{"type":725,"value":89300,"toc":89387},[89301,89303,89306,89315,89328,89337,89346,89350,89353,89356,89370,89373,89376,89379,89385],[842,89302,40867],{},[842,89304,89305],{},"Whatever your business may be, you can’t deny how much technology can help. Since our inception back in 2010, Bravelab.io has been helping small and mid-sized businesses scale through bespoke technology solutions. We deliver sophisticated and top-of-the-line solutions because of our expertise in Python, JavaScript, Vue.js, GraphQL, Jamstack, and more.",[842,89307,89308,89309,89314],{},"Company financial stability and profit which significantly exceeded set goals (4 mln PLN) helped Bravelab to expand! We successfully acquired new talents to the Development, HR, and Design teams and we are constantly searching for more, especially new Python and Vue.js developers (see more here: ",[846,89310,89313],{"href":89311,"rel":89312},"https://bravelab.recruitee.com/",[850],"Careers - Jobs - Bravelab.io",")!",[842,89316,89317,89318,89322,89323,89327],{},"Throughout the whole year, our teams have been developing modern Web Platforms using the latest technologies such as ",[846,89319,89321],{"href":88907,"rel":89320},[850],"Saleor.io"," - an e-commerce platform and ",[846,89324,89326],{"href":88342,"rel":89325},[850],"The Unified Content Platform – Sanity.io"," - a unified content platform.",[842,89329,89330,89331,89336],{},"Together with ",[846,89332,89335],{"href":89333,"rel":89334},"https://www.wearewundr.com/",[850],"Wundr Commerce Limited",", we’ve been implementing a revolutionary embedded e-commerce platform with lots of integrations with external data providers (Farfetch, BDroppy, Zendesk, Avalara, Beautyfort, Fragrance, Expansys, STC Pay, and more). From a long-term perspective, the platform will be a multi-tenant platform to manage millions of orders.",[842,89338,89339,89340,89345],{},"With ",[846,89341,89344],{"href":89342,"rel":89343},"https://pagepro.co/",[850],"Pagepro",", one of our key business partners, we’ve been working on a backend of a restaurant management application based on the Saleor platform. The application required the expansion and modification of the Saleor’s elements to suit the specificity of the restaurant industry, adding integration with external systems (Hubrise, Ikentoo) and enabling multi-tenant operations.",[863,89347,89349],{"id":89348},"bravelabio-core-technology-stack","Bravelab.io core technology stack",[842,89351,89352],{},"Our core competencies are still in Python and JavaScript, with a strong focus on Vue.js, GraphQL, and Jamstack solutions. This tech stack helps us to deliver tailor-made services for e-commerce.",[842,89354,89355],{},"For more than a decade, we’ve delivered over 90 successful projects to our appreciative clients. It’s because of their trust that we celebrate our memorable 2021 journey. Today, we’re taking a moment to pay homage and share with all of you our Clutch Year In Review!",[842,89357,89358,89363,89364,89369],{},[846,89359,89362],{"href":89360,"rel":89361},"https://clutch.co/",[850],"Clutch"," is a B2B review and market research platform that provides in-depth insights into various industries and locations. Last year, the Bravelab.io team was ",[846,89365,89368],{"href":89366,"rel":89367},"https://clutch.co/pl/developers/nodejs/krakow",[850],"recognised as"," one of Krakow’s leading Python & Vue.js developers thanks to our ever-supportive partners!",[842,89371,89372],{},"Firms that want to rank highly on the platform must earn top-notch reviews from clients to show their expertise and the quality of their work.** In 2021, we earned 5 comprehensive reviews that took a deeper dive into our partnerships. All of the reviews we’ve received average 5-star ratings and perfect NPS scores.**",[842,89374,89375],{},"NPS score is a metric that gauges how willing a client is to refer a vendor to colleagues and friends. Considering how crucial word of mouth is, it’s truly humbling to see how much our clients appreciate us. For them to say that are willing to recommend us shows that they trust our team’s capabilities.",[842,89377,89378],{},"Thank you to all our partners who took the time out of their busy schedules to review us. It’s because of your trust that we can achieve such feats. We owe you so much.",[50359,89380,89382],{"id":89381},"ready-to-take-on-2022-with-a-tried-and-tested-partner",[996,89383,89384],{},"Ready to take on 2022 with a tried and tested partner?",[842,89386,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":89388},[89389],{"id":89348,"depth":729,"text":89349},"2022-01-25T00:00:00.000Z","A look at Bravelab.io's Clutch 2021 highlights: key achievements, client reviews, and milestones in custom software development.",{"src":89393},"/images/blog/musictechlab_blog_a-look-at-musictechlab-ios-clutch-2021-year-in-review.webp",{"enabled":738,"items":89395},[89396,89398,89400],{"text":89397,"icon":3920},"Earned 5 comprehensive Clutch reviews in 2021, all averaging 5-star ratings.",{"text":89399,"icon":5504},"Revenue exceeded the 4 million PLN target, enabling team expansion.",{"text":89401,"icon":37696},"Delivered 90+ successful projects over more than a decade of operation.",{},{"title":330,"description":89391},[74615],"zzLxfOO1TgKyVHv8d8Ln8oNTa8X7j1UTsYeIshNOUls",{"id":89407,"title":22,"authors":723,"badge":89408,"body":89409,"category":4990,"client":89618,"date":89620,"description":89621,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":89622,"keyTakeaways":89624,"meta":89632,"navigation":738,"path":23,"seo":89633,"status":723,"stem":24,"tags":89634,"teaser":723,"__hash__":89635},"posts/blog/case-study/galeco-mobile-app-case-study.md",{"label":5,"color":50099},{"type":725,"value":89410,"toc":89604},[89411,89413,89416,89418,89459,89461,89475,89477,89480,89483,89485,89493,89495,89500,89506,89508,89516,89522,89528,89530,89533,89548,89550,89553,89555,89560,89562,89565,89567,89586,89588],[842,89412,40867],{},[842,89414,89415],{},"A mobile application for Android and iOS platforms. The application becomes an intuitive gutter systems handbook for roofers and architects.",[863,89417,65386],{"id":65385},[958,89419,89420,89425,89430,89434,89439,89443,89448,89454],{},[961,89421,89422,89424],{},[996,89423,34475],{}," - Backend development",[961,89426,89427,89429],{},[996,89428,65784],{}," - Python web framework",[961,89431,89432,87640],{},[996,89433,87645],{},[961,89435,89436,89438],{},[996,89437,64032],{}," - Mobile app development",[961,89440,89441,87634],{},[996,89442,65403],{},[961,89444,89445,89447],{},[996,89446,26084],{}," - Push notifications and backend services",[961,89449,89450,89453],{},[996,89451,89452],{},"Ubuntu Server (VPS)"," - Infrastructure",[961,89455,89456,89458],{},[996,89457,65407],{}," - Containerization",[863,89460,66002],{"id":66001},[958,89462,89463,89466,89469,89472],{},[961,89464,89465],{},"Mobile App Development",[961,89467,89468],{},"Backend Development",[961,89470,89471],{},"UI/UX Design",[961,89473,89474],{},"Testing & Maintenance",[863,89476,52035],{"id":52034},[842,89478,89479],{},"Galeco specialises in the design, production and distribution of roof gutters, gutter systems, rainwater drainage systems from flat roofs, as well as PVC roof soffit. The aim of the project was to develop a mobile application. The application is intended to help installers during assembly work.",[842,89481,89482],{},"At Galeco, they wanted to facilitate the work of roofers during the installation of gutters. Their work takes place in the field, usually on the roof. At that time, searching for paper versions of the instructions, or looking for them in general, was a significant obstacle and a waste of time for the contractor. The application available on the smartphone also in the offline version (if there is no Internet on the construction site) allows the roofer to access the instructions at any time, divided into steps and stages.",[863,89484,27663],{"id":52066},[842,89486,89487,89488,861],{},"Galeco App consists of a backend application. The backend was made with Django. It is used to manage users, Galeco's systems and takes care of user authentication. Managers are able to send push notifications. The application was made in React Native - which is an alternative for expensive native app development. It displays systems with particular stages and steps of system assembly. The application allows users to search gutter systems and gives them information about the system. It allows users to watch every step of assembly in 3D videos. The application also makes it possible to download a PDF manual as a complementary option. Moreover, registered users can save the video on their devices and play the installation instructions offline. The graphic design of the application was made in cooperation with ",[846,89489,89492],{"href":89490,"rel":89491},"https://dandelionstud.io/",[850],"dandelionstud.io",[863,89494,87697],{"id":87696},[41054,89496,89497],{},[842,89498,89499],{},"Our goal was to enable offline mode. Users should be able to download the video to their mobile phones. The custom push notification system was also a challenge. The project gave me many chances to prove my problem-solving skills.",[842,89501,89502,89505],{},[996,89503,89504],{},"Paweł Kotoniak"," - React Native Developer",[863,89507,66034],{"id":66033},[41054,89509,89510,89513],{},[842,89511,89512],{},"Their communication was flawless, and we always felt secured.",[842,89514,89515],{},"We worked with 3 people inside of Bravelab, who were available for us all the time. We shared our feedback constantly so we could work simultaneously on a few things at the same time.",[842,89517,89518,89521],{},[996,89519,89520],{},"Anna Góral"," - Senior Marketing Specialist, Galeco",[842,89523,89524],{},[846,89525,72338],{"href":89526,"rel":89527},"https://clutch.co/profile/musictech-lab?page=1#review-1795676",[850],[863,89529,87730],{"id":87729},[842,89531,89532],{},"Galeco App has been designed for a very specific group of users. After a deep research process and a top development process we have launched an app that will allow roofers, architects and contractors to speed up their work and avoid unnecessary mistakes during assembling the roof elements. Scope of the work:",[958,89534,89535,89538,89541,89544,89546],{},[961,89536,89537],{},"Concept idea and research",[961,89539,89540],{},"User interface and app design",[961,89542,89543],{},"App development",[961,89545,51017],{},[961,89547,14498],{},[863,89549,87684],{"id":87683},[842,89551,89552],{},"At Galeco, we wanted to facilitate the work of roofers during the installation of gutters. Their work takes place in the field, usually on the roof. At that time, searching for paper versions of the instructions, or looking for them in general, was a significant obstacle and a waste of time for the contractor. The application available on the smartphone also in the offline version (if there is no Internet on the construction site) allows the roofer to access the instructions at any time, divided into steps and stages.",[863,89554,87690],{"id":87689},[842,89556,89487,89557,861],{},[846,89558,89492],{"href":89490,"rel":89559},[850],[863,89561,52145],{"id":52144},[842,89563,89564],{},"The release of the application with gutter assembly instructions is a novelty for the roofing and construction industries, and Galeco wanted to consider themselves as pioneers in the industry. It can therefore be said that the application proves that they think in a modern, future-oriented way and, as Galeco, they are constantly looking for new solutions. Galeco intends to use it to conduct various promotional campaigns that will certainly build the Galeco's brand.",[863,89566,52192],{"id":52191},[958,89568,89569,89574],{},[961,89570,89571,89573],{},[996,89572,87755],{}," 3 months",[961,89575,89576,89578],{},[996,89577,87761],{},[958,89579,89580,89582,89584],{},[961,89581,87482],{},[961,89583,75384],{},[961,89585,72380],{},[863,89587,27869],{"id":27868},[958,89589,89590,89595,89600],{},[961,89591,89592],{},[846,89593,89594],{"href":307},"10 Benefits of outsourcing software development services",[961,89596,89597],{},[846,89598,89599],{"href":319},"5 steps to implement an effective communication strategy in outsourcing software development projects",[961,89601,89602],{},[846,89603,322],{"href":323},{"title":728,"searchDepth":729,"depth":729,"links":89605},[89606,89607,89608,89609,89610,89611,89612,89613,89614,89615,89616,89617],{"id":65385,"depth":729,"text":65386},{"id":66001,"depth":729,"text":66002},{"id":52034,"depth":729,"text":52035},{"id":52066,"depth":729,"text":27663},{"id":87696,"depth":729,"text":87697},{"id":66033,"depth":729,"text":66034},{"id":87729,"depth":729,"text":87730},{"id":87683,"depth":729,"text":87684},{"id":87689,"depth":729,"text":87690},{"id":52144,"depth":729,"text":52145},{"id":52191,"depth":729,"text":52192},{"id":27868,"depth":729,"text":27869},{"name":89619},"Galeco","2022-01-01T00:00:00.000Z","How we built an offline-capable React Native app for Galeco with 3D video assembly instructions for roofers and architects.",{"src":89623},"/images/cdn-migrated/bravelab-experience-galeco-mobile-app.webp",{"enabled":738,"items":89625},[89626,89628,89630],{"text":89627,"icon":4855},"Offline-capable React Native app with 3D video assembly instructions for roofers.",{"text":89629,"icon":3271},"Delivered in 3 months with a 3-person team: developer, designer, and backend engineer.",{"text":89631,"icon":37696},"First mobile assembly guide in the roofing industry, positioning Galeco as a pioneer.",{},{"title":22,"description":89621},[4990,18784],"79-gszCudS977pMHC7TrtYxVpR-LkAvGBI0ASsVFa18",{"id":89637,"title":30,"authors":723,"badge":89638,"body":89639,"category":4990,"client":89771,"date":89620,"description":89773,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":89774,"keyTakeaways":89776,"meta":89784,"navigation":738,"path":31,"seo":89785,"status":723,"stem":32,"tags":89786,"teaser":723,"__hash__":89787},"posts/blog/case-study/loyalty-program-application-case-study.md",{"label":5,"color":50099},{"type":725,"value":89640,"toc":89761},[89641,89643,89646,89648,89678,89680,89693,89695,89698,89701,89703,89706,89709,89711,89713,89716,89718,89720,89722,89724,89745,89747],[842,89642,40867],{},[842,89644,89645],{},"The loyalty program application for tablet devices allows issuing gift cards based on the total amount of completed purchases.",[863,89647,65386],{"id":65385},[958,89649,89650,89654,89660,89664,89668,89672],{},[961,89651,89652,89424],{},[996,89653,34475],{},[961,89655,89656,89659],{},[996,89657,89658],{},"JavaScript"," - Frontend development",[961,89661,89662,87634],{},[996,89663,65403],{},[961,89665,89666,89429],{},[996,89667,65784],{},[961,89669,89670,87640],{},[996,89671,87639],{},[961,89673,89674,89677],{},[996,89675,89676],{},"Barcode Generator"," - Card generation functionality",[863,89679,66002],{"id":66001},[958,89681,89682,89684,89687,89690],{},[961,89683,89468],{},[961,89685,89686],{},"Frontend Development",[961,89688,89689],{},"Tablet Application",[961,89691,89692],{},"Custom Software Development",[863,89694,52035],{"id":52034},[842,89696,89697],{},"The Forum shopping mall marketing department needed tools to carry out cyclical promotional campaigns related to issuing ATM prepaid cards to shopping mall customers.",[842,89699,89700],{},"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.",[863,89702,27663],{"id":52066},[842,89704,89705],{},"The application consists of three components: Backend API, Frontend Panel, and Tablet.",[842,89707,89708],{},"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.",[863,89710,87730],{"id":87729},[842,89712,89697],{},[842,89714,89715],{},"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.",[842,89717,89708],{},[863,89719,52145],{"id":52144},[842,89721,89708],{},[863,89723,52192],{"id":52191},[958,89725,89726,89731],{},[961,89727,89728,89730],{},[996,89729,87755],{}," 4 months",[961,89732,89733,89736],{},[996,89734,89735],{},"Components:",[958,89737,89738,89740,89743],{},[961,89739,52010],{},[961,89741,89742],{},"Frontend Panel",[961,89744,89689],{},[863,89746,27869],{"id":27868},[958,89748,89749,89753,89757],{},[961,89750,89751],{},[846,89752,89594],{"href":307},[961,89754,89755],{},[846,89756,89599],{"href":319},[961,89758,89759],{},[846,89760,322],{"href":323},{"title":728,"searchDepth":729,"depth":729,"links":89762},[89763,89764,89765,89766,89767,89768,89769,89770],{"id":65385,"depth":729,"text":65386},{"id":66001,"depth":729,"text":66002},{"id":52034,"depth":729,"text":52035},{"id":52066,"depth":729,"text":27663},{"id":87729,"depth":729,"text":87730},{"id":52144,"depth":729,"text":52145},{"id":52191,"depth":729,"text":52192},{"id":27868,"depth":729,"text":27869},{"name":89772},"Forum Gliwice","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.",{"src":89775},"/images/cdn-migrated/bravelab_experience_forum_gliwice_app.webp",{"enabled":738,"items":89777},[89778,89780,89782],{"text":89779,"icon":44496},"Tablet-based loyalty app running continuously since 2017 with zero core code changes.",{"text":89781,"icon":2939},"Built in 5-6 weeks with Backend API, Frontend Panel, and Tablet components.",{"text":89783,"icon":2895},"Database design allows new loyalty program editions without developer involvement.",{},{"title":30,"description":89773},[4990,52276],"BX8fBD_XaDt9QH0yN3vlZYyUvIVCVgiZW6CzI_K4hLw",{"id":89789,"title":46,"authors":723,"badge":89790,"body":89791,"category":4990,"client":90065,"date":89620,"description":90067,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":90068,"keyTakeaways":90070,"meta":90078,"navigation":738,"path":47,"seo":90079,"status":723,"stem":48,"tags":90080,"teaser":723,"__hash__":90081},"posts/blog/case-study/roofing-wholesalers-website-case-study.md",{"label":5,"color":50099},{"type":725,"value":89792,"toc":90051},[89793,89795,89798,89801,89804,89808,89811,89813,89853,89855,89869,89871,89874,89876,89879,89895,89905,89908,89911,89914,89916,89921,89927,89929,89931,89933,89935,89938,89942,89956,89960,89992,89994,89997,90000,90003,90005,90035,90037],[842,89794,40867],{},[842,89796,89797],{},"A platform built in Sanity for the most extensive network of roofing wholesalers in Poland.",[842,89799,89800],{},"Poland is living in a boom economic era, and the construction industry is at its peak. Many sectors and all size companies are demanding new solutions to expand their business and reach their goals. In this scenario, 4D Grupa comes up with the audacious idea to assemble a network of roofers from all over Poland to cover a niche market.",[842,89802,89803],{},"The ambitious project required designing and developing an intuitive and scalable platform to connect companies in this sector, advertise them, and launch an intuitive interface for potential clients to make wise choices during the product selection process.",[863,89805,89807],{"id":89806},"about-4d-grupa","About 4D Grupa",[842,89809,89810],{},"4D Grupa is an integrated network of roof wholesalers, which aims to create and develop the strongest brand on the roofing materials distribution market in Poland.",[863,89812,65386],{"id":65385},[958,89814,89815,89819,89824,89830,89836,89841,89847],{},[961,89816,89817,87629],{},[996,89818,87628],{},[961,89820,89821,89823],{},[996,89822,88470],{}," - Headless CMS",[961,89825,89826,89829],{},[996,89827,89828],{},"Headless"," - Headless architecture",[961,89831,89832,89835],{},[996,89833,89834],{},"Jamstack"," - Modern web architecture",[961,89837,89838,89840],{},[996,89839,72291],{}," - Hosting and deployment",[961,89842,89843,89846],{},[996,89844,89845],{},"GridsomeJs"," - Static site generator",[961,89848,89849,89852],{},[996,89850,89851],{},"SSR"," - Server-side rendering",[863,89854,66002],{"id":66001},[958,89856,89857,89860,89863,89866],{},[961,89858,89859],{},"Web Development",[961,89861,89862],{},"Platform Design",[961,89864,89865],{},"Headless CMS Implementation",[961,89867,89868],{},"Testing & Launch",[863,89870,52035],{"id":52034},[842,89872,89873],{},"Our client was looking to implement a new web platform to gather wholesalers from the construction sector in Poland. The new platform must integrate a system to promote the company's members through a perfectly designed site and include tutorials and news related to the 4D Grupa activities. Due to the expected reach and faster growth of this new network, one of the most challenging spots was to develop a platform easy to enhance, always keeping the magic essence of the intuitive interface the client desired from its conception.",[863,89875,27663],{"id":52066},[842,89877,89878],{},"At Bravelab, we create a transparent process where communication between the client and our team is fluid and effective. We fully immerse ourselves in each project, so we can have a comprehensive understanding of the scope and establish the main objectives of the project:",[958,89880,89881,89884,89887,89890,89893],{},[961,89882,89883],{},"Brief",[961,89885,89886],{},"Introduction call",[961,89888,89889],{},"Discovery Workshops",[961,89891,89892],{},"Integral Proposal",[961,89894,26212],{},[842,89896,89897,89898,1589,89901,89904],{},"The values that guided us were indeed ",[996,89899,89900],{},"partnership",[996,89902,89903],{},"mastery",". Thanks to that and our experience, we managed to fulfil the client's expectations.",[842,89906,89907],{},"We prepared the entire site with a leading-edge modular system, which should allow us to build in the future any number of subpages representing the offer of cooperating construction companies. The key challenge was to simplify the search results by designing the search site focused on nearby companies.",[842,89909,89910],{},"Bravelab built a cutting-edge solution using Sanity, a unified content platform that powers better digital experiences. Sanity is a perfect mix of ease-to-use paired with the ability to customise almost anything the client needs.",[842,89912,89913],{},"The 4D Grupa platform was developed with TypeScript. It allows us to build an innovative platform with strong static typing that makes it possible to find the right balance between flexibility and correctness.",[863,89915,87697],{"id":87696},[41054,89917,89918],{},[842,89919,89920],{},"Sanity is simple to use CMS for a customer. Thanks to it, our client can modify each text on its website without developer support. Moreover, it's easy to use with Gridsome via GraphQL API, allowing me to create content for specific pages with accurate data.",[842,89922,89923,89926],{},[996,89924,89925],{},"Bartosz"," - Developer, Bravelab",[863,89928,87730],{"id":87729},[842,89930,89907],{},[842,89932,89910],{},[842,89934,89913],{},[842,89936,89937],{},"The website required the implementation of unconventional and advanced animations. The challenge was to overcome the technical limitation by implementing tailored solutions. The interface was designed with the main intention to display all the information about the company associated with an interactive experience that allows users to discover 4D Grupa as an open and dynamic company.",[863,89939,89941],{"id":89940},"scope-of-the-work","Scope of the Work",[958,89943,89944,89947,89950,89953],{},[961,89945,89946],{},"Concept idea and research (by Jakub Szufnarowski / Dandelion)",[961,89948,89949],{},"User interface and platform design (by Jakub Szufnarowski / Dandelion)",[961,89951,89952],{},"Platform development",[961,89954,89955],{},"Testing & launch",[863,89957,89959],{"id":89958},"insight-numbers","Insight Numbers",[958,89961,89962,89967],{},[961,89963,89964],{},[996,89965,89966],{},"263 hours invested on app development",[961,89968,89969,89972],{},[996,89970,89971],{},"6 Developers assigned to this project:",[958,89973,89974,89977,89980,89983,89986,89989],{},[961,89975,89976],{},"Paweł - Frontend",[961,89978,89979],{},"Jakub - Frontend",[961,89981,89982],{},"Konrad - Full stack",[961,89984,89985],{},"Bartosz - Frontend",[961,89987,89988],{},"Maciej - Frontend",[961,89990,89991],{},"Jakub - UX/UI Senior Designer",[863,89993,52145],{"id":52144},[842,89995,89996],{},"We achieved a platform that is fast and easy to use. It does not need any maintenance. The website can now be a place to work together with wholesalers and has a simple and intuitive design. Our client has a site using the newest technology in programming and can do everything by himself.",[842,89998,89999],{},"By implementing the know-how section and tutorials, every user can gather knowledge about each aspect of the construction and choose the right offer.",[842,90001,90002],{},"A most significant advantage was to design the interface clean from one side, and the other side will be just the frame for presenting the partners offer which is on a very diverse visual level.",[863,90004,52192],{"id":52191},[958,90006,90007,90012],{},[961,90008,90009,90011],{},[996,90010,87755],{}," 12 months",[961,90013,90014,90016],{},[996,90015,87761],{},[958,90017,90018,90021,90024,90027,90030,90033],{},[961,90019,90020],{},"Paweł - Frontend Developer",[961,90022,90023],{},"Jakub - Frontend Developer",[961,90025,90026],{},"Konrad - Full Stack Developer",[961,90028,90029],{},"Bartosz - Frontend Developer",[961,90031,90032],{},"Maciej - Frontend Developer",[961,90034,89991],{},[863,90036,27869],{"id":27868},[958,90038,90039,90043,90047],{},[961,90040,90041],{},[846,90042,89594],{"href":307},[961,90044,90045],{},[846,90046,89599],{"href":319},[961,90048,90049],{},[846,90050,322],{"href":323},{"title":728,"searchDepth":729,"depth":729,"links":90052},[90053,90054,90055,90056,90057,90058,90059,90060,90061,90062,90063,90064],{"id":89806,"depth":729,"text":89807},{"id":65385,"depth":729,"text":65386},{"id":66001,"depth":729,"text":66002},{"id":52034,"depth":729,"text":52035},{"id":52066,"depth":729,"text":27663},{"id":87696,"depth":729,"text":87697},{"id":87729,"depth":729,"text":87730},{"id":89940,"depth":729,"text":89941},{"id":89958,"depth":729,"text":89959},{"id":52144,"depth":729,"text":52145},{"id":52191,"depth":729,"text":52192},{"id":27868,"depth":729,"text":27869},{"name":90066},"4D Grupa","How we built a scalable headless CMS platform in Sanity with TypeScript and Gridsome for Poland's largest roofing wholesalers network.",{"src":90069},"/images/cdn-migrated/bravelab_experience_4dgrupa.webp",{"enabled":738,"items":90071},[90072,90074,90076],{"text":90073,"icon":85476},"Headless CMS platform built with Sanity and TypeScript for Poland's largest roofing network.",{"text":90075,"icon":3271},"263 hours of development with a 6-person team over 12 months.",{"text":90077,"icon":13562},"Client can modify all website content without developer support via Sanity CMS.",{},{"title":46,"description":90067},[4990,52276],"hUE9KyGnt5qQGdDLpADHwUQC4UYG3j9RLg0mOQuMLts",{"id":90083,"title":54,"authors":723,"badge":90084,"body":90085,"category":4990,"client":90204,"date":89620,"description":90206,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":90207,"keyTakeaways":90209,"meta":90217,"navigation":738,"path":55,"seo":90218,"status":723,"stem":56,"tags":90219,"teaser":723,"__hash__":90220},"posts/blog/case-study/ticketing-events-platform-case-study.md",{"label":5,"color":50099},{"type":725,"value":90086,"toc":90195},[90087,90090,90093,90095,90106,90110,90113,90116,90121,90123,90128,90134,90136,90141,90147,90153,90155,90158,90160,90179,90181],[842,90088,90089],{},"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.",[842,90091,90092],{},"The internal team lacked engineers who could handle this work — but the integrations were a key revenue driver.",[863,90094,65386],{"id":65385},[1045,90096,90098,90100,90104],{"className":90097},[1048,1049,1050,1051,1052],[1054,90099],{"description":72153,"title":72154},[1054,90101],{"description":90102,"title":90103},"Asynchronous programming","asyncio",[1054,90105],{"description":87384,"title":64259},[863,90107,90109],{"id":90108},"how-we-worked","How We Worked",[842,90111,90112],{},"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.",[842,90114,90115],{},"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.",[1032,90117,90118],{},[842,90119,90120],{},"The client was positively surprised by the velocity. Working solutions were delivered within weeks of starting.",[863,90122,87697],{"id":87696},[41054,90124,90125],{},[842,90126,90127],{},"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.",[842,90129,90130,90133],{},[996,90131,90132],{},"Szymon Zmilczak"," — Python Senior Developer",[863,90135,66034],{"id":66033},[41054,90137,90138],{},[842,90139,90140],{},"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.",[842,90142,90143,90146],{},[996,90144,90145],{},"Paweł Małkowiak"," — Co-Founder, Solidstudio",[842,90148,90149],{},[846,90150,72338],{"href":90151,"rel":90152},"https://clutch.co/profile/musictech-lab#review-1066514",[850],[863,90154,52145],{"id":52144},[842,90156,90157],{},"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.",[863,90159,52192],{"id":52191},[958,90161,90162,90168,90174],{},[961,90163,90164,90167],{},[996,90165,90166],{},"Duration:"," 24+ months",[961,90169,90170,90173],{},[996,90171,90172],{},"Services:"," Backend Development, API Integration, IT Staff Augmentation",[961,90175,90176,90178],{},[996,90177,87761],{}," Oleksandr, Paweł, Jakub, Szymon — Python Developers",[863,90180,27869],{"id":27868},[958,90182,90183,90187,90191],{},[961,90184,90185],{},[846,90186,89594],{"href":307},[961,90188,90189],{},[846,90190,89599],{"href":319},[961,90192,90193],{},[846,90194,322],{"href":323},{"title":728,"searchDepth":729,"depth":729,"links":90196},[90197,90198,90199,90200,90201,90202,90203],{"id":65385,"depth":729,"text":65386},{"id":90108,"depth":729,"text":90109},{"id":87696,"depth":729,"text":87697},{"id":66033,"depth":729,"text":66034},{"id":52144,"depth":729,"text":52145},{"id":52191,"depth":729,"text":52192},{"id":27868,"depth":729,"text":27869},{"name":90205},"Solidstudio","How we provided a Python development team to build third-party API integrations for a ticketing platform, through long-term team augmentation.",{"src":90208},"/images/cdn-migrated/bravelab_experience_solidstudio.webp",{"enabled":738,"items":90210},[90211,90213,90215],{"text":90212,"icon":5365},"Over a dozen third-party API integrations delivered from zero within months.",{"text":90214,"icon":11617},"24+ month team augmentation engagement with 4 Python developers.",{"text":90216,"icon":3920},"Each integration directly translated into new revenue for the client.",{},{"title":54,"description":90206},[4990,52276],"7q7_6IaklPlqwPe87srrU9NNva15SSSc6KAuLdMN7aA",{"id":90222,"title":554,"authors":90223,"badge":723,"body":90228,"category":756,"client":723,"date":90559,"description":90560,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":90561,"keyTakeaways":90563,"meta":90571,"navigation":738,"path":555,"seo":90572,"status":723,"stem":556,"tags":90573,"teaser":723,"__hash__":90574},"posts/blog/software-development/is-it-possible-to-use-vue-js-as-a-frontend-for-saleor-io-platform.md",[90224],{"name":90225,"avatar":90226},"Olga Wałkuska",{"src":90227},"/images/people/olga-walkuska.png",{"type":725,"value":90229,"toc":90545},[90230,90232,90235,90238,90245,90248,90252,90255,90258,90261,90265,90268,90271,90274,90277,90280,90284,90287,90289,90291,90294,90297,90300,90303,90306,90309,90312,90316,90319,90322,90326,90329,90340,90343,90346,90349,90352,90355,90364,90367,90370,90374,90377,90380,90383,90386,90389,90393,90396,90399,90402,90405,90408,90412,90415,90418,90422,90425,90429,90443,90446,90450,90470,90473,90477,90480,90484,90498,90502,90516,90519,90523,90526,90529,90532,90535,90537,90540,90543],[842,90231,40867],{},[842,90233,90234],{},"We believe that Saleor is one of the most interesting e-commerce solutions currently available on the market. And, contrary to what one might think, our opinion is not at all biased by the fact that we helped to write it.",[842,90236,90237],{},"If you want to learn the reasoning behind our belief, we invite you to take a look at our",[842,90239,90240,90244],{},[846,90241,90243],{"href":90242},"/blog/9-reasons-why-the-saleor-io-platform-is-the-best-choice-for-your-ecommerce-website","first piece about Saleor."," We devoted it to discussing the ways in which Saleor can help online businesses thrive.",[842,90246,90247],{},"This article will focus on choosing the right front end to maximize Saleor’s performance.",[5539,90249,90251],{"id":90250},"saleor-is-headless-what-it-means","Saleor is Headless - What it Means?",[842,90253,90254],{},"First of all, let’s discuss the very concept of a headless solution. What does it mean that Saleor is headless? Is it good or bad?",[842,90256,90257],{},"Let’s answer those questions one by one.",[842,90259,90260],{},"A headless solution is a kind of software product that only consists of a back end. To be useful to end-users, it needs to be paired up with the front end. How does it work?",[863,90262,90264],{"id":90263},"front-end-and-back-end-you-cant-have-one-without-the-other","Front End and Back End - You Can’t Have One Without the Other",[842,90266,90267],{},"Almost every software consists of two layers - front end and back end. (When you think of a world, you might come to the conclusion that the above statement can be applied to much more than software).",[842,90269,90270],{},"The back end is responsible for computations. It does the work that has to be done. Processes the data and performs all the necessary calculations.",[842,90272,90273],{},"The front end is what is seen by the users. The front end presents the outcome of the work done by the back end in a user-friendly way.",[842,90275,90276],{},"Disclaimer: that is, of course, a very simplified way of describing those interactions.",[842,90278,90279],{},"In a headless solution, the back end and front end are entirely independent and only communicate with each other with the use of application programming interfaces (APIs).",[863,90281,90283],{"id":90282},"is-headless-good","Is Headless Good?",[842,90285,90286],{},"The answer to this question is straightforward and, unfortunately, very annoying - it depends. Or, in other words, such a solution has its pros and cons.",[1074,90288],{"id":728},[842,90290,67026],{},[842,90292,90293],{},"The first conclusion that comes to mind is that a headless solution is just half of the software needed for the job. That’s definitely a disadvantage.",[842,90295,90296],{},"What are the advantages to outweigh it?",[1074,90298,84388],{"id":90299},"security",[842,90301,90302],{},"Decoupling the front end and back end increases the level of security of the entire system. Since all the operations are performed by the back end and only the outcome is exposed via the API - the system is much harder to hack. In an e-commerce solution, that is undeniably a big plus.",[1074,90304,48566],{"id":90305},"customization",[842,90307,90308],{},"Another one is the unlimited possibility of customization. With the custom front end, the e-commerce solution is bound to be one of a kind. There is no way for the two shops to look the same because the owners chose the same template.",[842,90310,90311],{},"But the interface is not the only thing that can be tailored to specific needs. The perfect front end should implement the exact number of features that are needed for the particular business. It’s the end of confusing options and non-intuitive navigation.",[1074,90313,90315],{"id":90314},"control","Control",[842,90317,90318],{},"Finally, people like what they already know. When the front end and back end come as a package, you have no influence over the changes implemented by the provider. So one day, your business might get an entirely new look. When you use a headless solution, changes in the back end do not influence what your customers see. You are the person in charge.",[842,90320,90321],{},"Now you know that if you want to choose Saleor, you will need to get yourself a front end. What are your options?",[5539,90323,90325],{"id":90324},"three-ways-to-approach-front-end","Three Ways to Approach Front End",[842,90327,90328],{},"When it comes to the front end for Saleor, there are three options for you to choose from:",[958,90330,90331,90334,90337],{},[961,90332,90333],{},"Saleor Storefront",[961,90335,90336],{},"Custom front end",[961,90338,90339],{},"Customizable ready-made solution",[842,90341,90342],{},"Later in this chapter, we’re going to discuss each of them in more detail, listing their pros and cons.",[842,90344,90345],{},"We were the ones stressing the influence of the front-end choices on the overall Saleor performance. Hence we will do our best for this section to be as detailed as possible.",[842,90347,90348],{},"Let’s take a look at the front end that comes with Saleor. Is it a valid option to consider?",[1074,90350,90333],{"id":90351},"saleor-storefront",[842,90353,90354],{},"From the title of this section, one might think that the demo front end built by the Saleor team is the best front end one might get for this e-commerce solution. End of story. But if that was the case, why would Saleor’s team consistently call their product a headless e-commerce solution?",[842,90356,90357,90358,90363],{},"The answer is simple: Saleor IS the headless solution. The demo front-end project is precisely what it’s called - a demo. The Saleor team doesn’t even recommend using it as a starter for writing the custom solution. ",[846,90359,90362],{"href":90360,"rel":90361},"https://github.com/saleor/saleor/discussions/8383",[850],"They are open"," about it being obsolete and carrying a lot of technical debt.",[842,90365,90366],{},"Those who want to see how Saleor’s team approaches the front end compatible with their product will appreciate that they are working on the new demo front end implementation. However, the release date is unknown, and they keep claiming it is still going to serve the demo purpose solely.",[842,90368,90369],{},"Given the above, the Saleor Storefront can only serve as an inspiration (and not even a solid base) for building the custom front end. After all, Saleor is a headless solution.",[1074,90371,90373],{"id":90372},"custom-front-end","Custom Front End",[842,90375,90376],{},"Since there is no ready-made front end from the creators of Saleor, if you want to use it, you need to get the front end elsewhere. The most straightforward option is to build one in-house or outsource it to the software provider.",[842,90378,90379],{},"Such a decision comes with a lot of upsides. It allows for complete customization. Not only concerning the user interface but also the optimal adoption of Saleor’s features. Custom solutions are tailored to your business needs in every detail. Plus, you can provide your users/clients with the perfect UI consistent with other communication channels.",[842,90381,90382],{},"Technology-wise, custom solutions give you the option to choose the one that's perfect for your endeavor. It’s up to you and/or your provider to decide if you go for the newest or the safest one or if you try to strike the perfect balance between both.",[842,90384,90385],{},"Unfortunately, custom solutions come with a significant drawback - they tend to be costly. And, they need to be updated whenever Saleor is.",[842,90387,90388],{},"So is there any other way to have a front end for your Saleor?",[1074,90390,90392],{"id":90391},"customizable-ready-made-solution","Customizable Ready-Made Solution",[842,90394,90395],{},"Building the Saleor front end from scratch is a considerable investment, especially for smaller organizations. Since we are the ones doing it for our clients, we understand it very well. That’s how we realized there is a market for another solution. The robust modular e-commerce back end like Saleor needs a powerful, modular front end solution. This is how we came up with the idea of Vueshop.io.",[842,90397,90398],{},"It is meant to serve as a customizable Saleor front end that can be implemented almost out of the box. It doesn’t need a UX designer, a qualified technical team, or a graphic designer. All of their work was done upfront by our team.",[842,90400,90401],{},"When it comes to customization, it’s up to you whether you choose from the templates or hire a graphical designer to prepare something unique.",[842,90403,90404],{},"Such a solution does not come with all the perks of a fully custom one. On the other hand, though, it is much cheaper and, above all, hassle-free.",[842,90406,90407],{},"The next section will help you better understand the difference between custom and customizable.",[5539,90409,90411],{"id":90410},"comparison-of-custom-and-ready-made","Comparison of Custom and Ready-Made",[842,90413,90414],{},"To the disappointment of many organizations, Saleor does not come with the front end. The only thing that’s available from their team is a demo that’s not even recommended to serve as a template for the custom solution. This means that when it comes to using Saleor for your business, there are only two possible front-end choices: custom front end and ready-made, customizable solution.",[842,90416,90417],{},"This chapter is going to discuss the pros and cons of each of these options in detail, letting you fully understand the differences.",[863,90419,90421],{"id":90420},"the-advantages","The Advantages",[842,90423,90424],{},"Let’s look on the bright side and start with the advantages of each of the solutions. Contrary to common-sense belief, a custom solution does not always have to beat the generic one.",[50359,90426,90428],{"id":90427},"the-pros-of-the-custom-front-end","The Pros of the Custom Front End",[958,90430,90431,90434,90437,90440],{},[961,90432,90433],{},"It’s perfectly tailored to your needs.",[961,90435,90436],{},"You have complete control over the technology used.",[961,90438,90439],{},"You decide on the graphical design (colors, layout, other graphical elements).",[961,90441,90442],{},"You can customize the UX (user experience) (in ready-made solutions, the layout is often pre-defined).",[842,90444,90445],{},"You can design the CX (customer experience) to match your other channels.",[50359,90447,90449],{"id":90448},"the-pros-of-the-ready-made-front-end","The Pros of the Ready-made Front End",[958,90451,90452,90455,90458,90461,90464,90467],{},[961,90453,90454],{},"It’s available instantly. You don’t have to wait for it to be written.",[961,90456,90457],{},"It uses a proven technological stack.",[961,90459,90460],{},"There is a team constantly working on improvements.",[961,90462,90463],{},"It gets updates matching the Saleor back end.",[961,90465,90466],{},"It’s budget-friendly.",[961,90468,90469],{},"It’s UX (user experience) optimized.",[842,90471,90472],{},"It can be seen at first sight that the lists of advantages of both solutions are of similar sizes. Let’s go to the next paragraph to examine the faults of each of them and make the picture even more transparent.",[863,90474,90476],{"id":90475},"the-disadvantages","The Disadvantages",[842,90478,90479],{},"What are the cons of each of the discussed solutions? Does a fully custom front end has any other downside than the cost? Let’s find out.",[50359,90481,90483],{"id":90482},"the-cons-of-the-custom-front-end","The Cons of the Custom Front End",[958,90485,90486,90489,90492,90495],{},[961,90487,90488],{},"You need to hire a team of developers or an external provider",[961,90490,90491],{},"Every time there is an update, your team needs to write an update too. There are no automatic updates.",[961,90493,90494],{},"Hiring a dev team or an external provider is costly",[961,90496,90497],{},"Writing a custom front end takes time",[50359,90499,90501],{"id":90500},"the-cons-of-the-ready-made-front-end","The Cons of the Ready-made Front End",[958,90503,90504,90507,90510,90513],{},[961,90505,90506],{},"The ready-made front end is not fully customizable",[961,90508,90509],{},"With the ready-made solution, your business does not get the unique look",[961,90511,90512],{},"Choosing the ready-made, customizable front end means you have no control over the technology used",[961,90514,90515],{},"It might not be possible to match the ready-made front end with your other channels to provide consistent CX",[842,90517,90518],{},"Apparently, the list of disadvantages of both solutions is similar in length too. This means that the right choice for your business has to be based on the criteria unique for the case. There is no simple answer on which one is better.",[5539,90520,90522],{"id":90521},"vuejs-the-future-of-front-end","Vue.js - The Future of Front End",[842,90524,90525],{},"Writing about the front end for Saleor e-commerce, we cannot skip mentioning Vue.js. At first sight, those two topics may not seem connected, but they have more in common than one might think.",[842,90527,90528],{},"First of all, as can be easily inferred from the name, we used Vue.js to build our project Vueshop.io. So if you decide to go for this customizable ready-made solution, you are choosing Vue.js as your front-end framework by default. We have to admit we think it’s working perfectly, giving end-users the kind of experience they expect from modern e-commerce solutions. What’s important its performance is equally good on desktop and mobile devices.",[842,90530,90531],{},"Bur Vueshop.io is not our only project. As we’ve mentioned before, we saw the need for it because our bread and butter is building Saleor front ends for various clients. We found Vue.js perfect for this job. It is lightweight, fast, and pleasant to work with. It allows for seamless cooperation between software engineers and designers, which is crucial for building appealing UIs. And last, but not least it speeds up the development. And in software development, nothing is more accurate than “time is money.”",[842,90533,90534],{},"So no matter if you choose to build the custom Saleor front end from scratch or go for our Vueshop, Vue.js is a technology to consider.",[5539,90536,25699],{"id":8196},[842,90538,90539],{},"Saleor is one of the most interesting modern e-commerce solutions. However, its most significant feature - being headless - may, for some, be its biggest fault. There are companies that find developing the custom front end to work with Saleor too significant an investment. To make Saleor more approachable for such players, we propose Vueshop.io - a ready-made, customizable front-end solution.",[842,90541,90542],{},"Written in Vue.js, Vueshop.io allows all organizations to enjoy the full capability of Saleor without worrying about the front end. It just matches Saleor's robust back end with an equally powerful UI.",[842,90544,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":90546},[90547,90548,90557,90558],{"id":90263,"depth":729,"text":90264},{"id":90282,"depth":729,"text":90283,"children":90549},[90550,90551,90552,90553,90554,90555,90556],{"id":728,"depth":1112,"text":728},{"id":90299,"depth":1112,"text":84388},{"id":90305,"depth":1112,"text":48566},{"id":90314,"depth":1112,"text":90315},{"id":90351,"depth":1112,"text":90333},{"id":90372,"depth":1112,"text":90373},{"id":90391,"depth":1112,"text":90392},{"id":90420,"depth":729,"text":90421},{"id":90475,"depth":729,"text":90476},"2021-12-01T00:00:00.000Z","Exploring whether Vue.js works as a frontend for the headless Saleor.io e-commerce platform. We compare it against React and share our implementation approach.",{"src":90562},"/images/blog/musictechlab_blog_is-it-possible-to-use-vue-js-as-a-frontend-for-saleor-io-platform.webp",{"enabled":738,"items":90564},[90565,90567,90569],{"text":90566,"icon":1769},"Saleor is headless, so it ships no production frontend and requires a custom or ready-made solution.",{"text":90568,"icon":85476},"Custom frontends offer full control but cost more; ready-made options trade flexibility for speed.",{"text":90570,"icon":2939},"Vue.js is lightweight and fast, making it a strong choice for Saleor e-commerce frontends.",{},{"title":554,"description":90560},[52276,18784],"LD_9sMbmFEGJWh4kfkPOey7OF09AvR7Co0s7ZQN1PTc",{"id":90576,"title":638,"authors":90577,"badge":723,"body":90582,"category":756,"client":723,"date":90652,"description":90653,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":90654,"keyTakeaways":90656,"meta":90664,"navigation":738,"path":639,"seo":90665,"status":723,"stem":640,"tags":90666,"teaser":723,"__hash__":90667},"posts/blog/software-development/the-gender-gap-in-the-tech-industry.md",[90578],{"name":90579,"avatar":90580},"Agnieszka Fierek",{"src":90581},"/images/people/agnieszka-fierek.webp",{"type":725,"value":90583,"toc":90648},[90584,90586,90595,90601,90605,90608,90613,90616,90620,90623,90631,90639,90646],[842,90585,40867],{},[842,90587,90588,90589,90591,90592,90594],{},"In the last decades, the world has seen the onward march of progress regarding the involvement of women in the workforce. Currently, women make up almost half (46.9%) of the total workforce.",[1086,90590,4434],{}," Yet, the tech industry falls behind the global job market in terms of hiring women: the five tech giants (Amazon, Apple, Facebook, Google, and Microsoft) hire about 34.4% women, while the percentage of employed women in all job sectors in the US has risen to 47%.",[1086,90593,1483],{}," Although it should not be the gender but rather the qualities of an individual that determine the choice of the career path, the current tech market conditions discourage women from assuming roles within the industry.",[842,90596,90597,90598,90600],{},"In 2018, women held only 25% of all the jobs in the tech industry, showing a decline in comparison to the 1980s.",[1086,90599,7948],{},"The gender gap has been manifested through various aspects. Not only women are less likely to study STEM subjects and are considered 22% more likely to experience \"imposter syndrome\" in the workplace, making them leave the jobs; another reason why women often exit the tech industry is the gender discrimination at work (reported by 50% of women), in the recruitment process (reported by 48%), and the perception of no clear career path forward for women (reported by 66%). All these aspects not only contribute to exiting the workplace but also to not even setting a foot in the tech industry.",[863,90602,90604],{"id":90603},"a-glass-half-full","A Glass Half Full",[842,90606,90607],{},"Some of us, however, manage to go against the trend. As a young woman attending middle school, I was fascinated by scientific subjects. By acknowledging the quality of my analytical skills, I could further pursue the career I was good at: I enrolled in a computer science degree, taking to Java, Python, and Django framework. At the university, I completed my first projects on programming languages. Most importantly, I have not stopped finessing my work and developing my passions. Nothing that could discourage me: regardless of the industry I was in, it was my hard work, determination, and predispositions that led me to the career I can today express myself in.",[842,90609,90610,90611],{},"Thus, I believe the prospects of women in IT should not be preached doom-and-gloom that quickly. Despite the long road to reach gender equality, there is a positive trend in the tech industry. Tech giants began to pay more attention to parental leave policies, ensuring that new mothers continue their career pursuits. At the same time, the earnings of women are outpacing those of men regarding high-skill jobs.",[1086,90612,7989],{},[842,90614,90615],{},"Meanwhile, the women-only tech groups swiftly develop across the countries, functioning as significant fora for women to network, discuss the challenges and empower each other. Throughout my studies, I have become a part of organizations dedicated to women in IT. Surrounded by strong women, I have learned how to lift other leading female figures up. Empowered, I considerably developed as a person, learning the importance of long-life learning. Today, I proudly translate this power onto my career as a front-end developer in Bravelab - a company fostering open-mindedness, transparency, and inclusion - where women constitute 25% of the employees and rising.",[863,90617,90619],{"id":90618},"have-we-always-done-it-this-way","Have we always done it this way?",[842,90621,90622],{},"Acknowledging the outdated behavioral patterns and archaic working methods, a technology pioneer, Grace Hopper, once said, “The most dangerous phrase in the language is ‘We’ve always done it this way.’” Hopper has brought to our attention the importance of new ideas, particularly those of women, who have long been deprived of a voice. That is why making the tech industry more attractive to women would be a groundbreaking development within the tech sector that, after all, praises innovation. Based on my experience, I know this approach is possible to be adopted around the world.",[842,90624,90625,7826,90627],{},[1086,90626,4434],{},[846,90628,90629],{"href":90629,"rel":90630},"https://www.catalyst.org/research/women-in-the-workforce-global/#:~:text=Globally%2C%20in%202020%3A,participants%20in%20the%20labor%20force",[850],[842,90632,90633,7826,90635],{},[1086,90634,1483],{},[846,90636,90637],{"href":90637,"rel":90638},"https://builtin.com/women-tech/women-in-tech-workplace-statistics",[850],[842,90640,90641,7826,90643],{},[1086,90642,7948],{},[846,90644,90637],{"href":90637,"rel":90645},[850],[842,90647,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":90649},[90650,90651],{"id":90603,"depth":729,"text":90604},{"id":90618,"depth":729,"text":90619},"2021-11-16T00:00:00.000Z","Is gender determine a career path? Or is it rather hard work, determination, and predispositions that led to a career in the tech industry?",{"src":90655},"/images/blog/musictechlab_blog_the-gender-gap-in-the-tech-industry.webp",{"enabled":738,"items":90657},[90658,90660,90662],{"text":90659,"icon":3844},"Women held only 25% of tech jobs in 2018, down from higher levels in the 1980s.",{"text":90661,"icon":3847},"50% of women in tech report gender discrimination at work; 66% see no clear career path.",{"text":90663,"icon":4845},"Women-only tech groups and improved parental leave policies are driving positive change.",{},{"title":638,"description":90653},[74615],"csfqGqO8bfmk58GZx30ejenzbhsVcyD_585B4RzQ-jE",{"id":90669,"title":658,"authors":90670,"badge":723,"body":90675,"category":756,"client":723,"date":90752,"description":90753,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":90754,"keyTakeaways":90756,"meta":90764,"navigation":738,"path":659,"seo":90765,"status":723,"stem":660,"tags":90766,"teaser":723,"__hash__":90767},"posts/blog/software-development/uber-101-how-this-ride-sharing-behemoth-made-it-to-the-top.md",[90671],{"name":90672,"avatar":90673},"Roberto Cruz",{"src":90674},"/images/people/roberto-cruz.webp",{"type":725,"value":90676,"toc":90747},[90677,90679,90682,90686,90689,90692,90696,90699,90706,90710,90717,90724,90727,90733,90739,90745],[842,90678,40867],{},[842,90680,90681],{},"What do you do when you want to get a cab? It’s rather unlikely you search the Internet trying to find a taxi number. Instead, your finger moves around the list of applications on your smartphone in the search for a taxi app. More often than not, it lands on the Uber app. Within 5 seconds, you can insert the destination and be automatically provided with your ride cost. The map of your city pops out on the screen, showing where you are going. It is just nothing but simple. So are Uber solutions that led the company to the top, making the app a new way of living!",[863,90683,90685],{"id":90684},"what-stands-behind-the-massive-success-of-uber","What stands behind the massive success of Uber?",[842,90687,90688],{},"Uber, a ride-sharing company with a net worth of $15 billion but without ownership of a single cab, has raised many eyebrows when it entered unique global markets. Since 2009, the timeworn taxi system has started to crumble. Yet, if you have lived in an international city at that time, you would have already been familiar with the brave new world of Uber.",[842,90690,90691],{},"Early on, the founders were lucky: Uber was financed by angel investors and turned out to be a relatively asset-light firm. As drivers brought their own cars and riders came with their own phones, Uber has become the world's biggest car service that does not own any cars. Yet, we still keep wondering: What is the recipe for Uber’s spectacular success?",[863,90693,90695],{"id":90694},"revolution-of-genuine-solutions","Revolution of genuine solutions",[842,90697,90698],{},"First entrants in the market have a** natural competitive advantage** - so had Uber as the first ride-sharing company. Being a pioneer, it could have carved out a large market share. When other emerging ridesharing providers, like Lyft and SideCar, have joined the race, Uber was already ahead - also in the minds of consumers. Not without reason, it has generated 25 times as much revenue as Lyft.",[842,90700,90701,90702,90705],{},"But what made Uber further climb the ladder of success are its ",[996,90703,90704],{},"authentic solutions"," to the problems of traditional cab companies. The firm has found the middle ground - it provided rides at a lower cost yet with a higher level of service. To tackle the traditional accountability concerns, Uber allowed consumers to rate their experience with drivers, skillfully leveraging the word of mouth.",[863,90707,90709],{"id":90708},"excellent-listening-skills","Excellent listening skills",[842,90711,90712,90713,90716],{},"A report from Nielsen shows that 84% of customers take action from personal recommendations. Well aware of the importance of ",[996,90714,90715],{},"customer feedback",", Uber - in its early days - compensated both people who referred their friends and the new users who tried the service.",[842,90718,90719,90720,90723],{},"While cheaply building the base of customers, the company applied the Disruptive Innovation model: Uber transformed the market by introducing ",[996,90721,90722],{},"convenience and affordability"," where the complication was the status quo. In effect, you can now be three clicks away from requesting a car: entering a pick-up address, setting up the location, and finally ordering a specific car. No unnecessary complexities on your way home.",[842,90725,90726],{},"With these 4 strategies based on simplicity and intuitiveness, Uber made it to the top. Cost-effectiveness and convenience have become the art of Uber’s product design. The initial idea of “luxury taxis” got replaced with a “better ride experience.” Yet, the success story of the company does not revolve around its service - it works mainly for the benefit of its customers, offering a great user experience and comfort for people. It is more than an app on your phone; it changes people's habits and lifestyles.",[842,90728,90729],{},[846,90730,90731],{"href":90731,"rel":90732},"https://mddailyrecord.com/uber-net-worth-2021-2022-2023",[850],[842,90734,90735],{},[846,90736,90737],{"href":90737,"rel":90738},"https://www.linkedin.com/pulse/10-lessons-startups-can-learn-from-ubers-growth-eric?trk=portfolio_article-card_title",[850],[842,90740,90741],{},[846,90742,90743],{"href":90743,"rel":90744},"https://www.nielsen.com/eu/en/press-releases/2015/recommendations-from-friends-remain-most-credible-form-of-advertising/",[850],[842,90746,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":90748},[90749,90750,90751],{"id":90684,"depth":729,"text":90685},{"id":90694,"depth":729,"text":90695},{"id":90708,"depth":729,"text":90709},"2021-10-25T00:00:00.000Z","What drives Uber's massive success? From first-mover advantage to authentic solutions that outperformed traditional cab companies and won customer trust.",{"src":90755},"/images/blog/musictechlab_blog_uber-101-how-this-ride-sharing-behemoth-made-it-to-the-top.webp",{"enabled":738,"items":90757},[90758,90760,90762],{"text":90759,"icon":3920},"Uber generated 25x more revenue than Lyft by leveraging first-mover advantage in ride-sharing.",{"text":90761,"icon":4845},"84% of customers act on personal recommendations, which Uber amplified with referral incentives.",{"text":90763,"icon":5504},"Uber succeeded by offering lower cost with higher service, disrupting traditional cab companies.",{},{"title":658,"description":90753},[18784],"Wob0SbLDZOKlR_NIDItMDma37Zq3YGq2SWA4uIwiXlg",{"id":90769,"title":446,"authors":90770,"badge":723,"body":90775,"category":756,"client":723,"date":90870,"description":90871,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":90872,"keyTakeaways":90874,"meta":90884,"navigation":738,"path":447,"seo":90885,"status":723,"stem":448,"tags":90886,"teaser":723,"__hash__":90887},"posts/blog/software-development/django-cms-and-graphql.md",[90771],{"name":90772,"avatar":90773},"Michał Dziadowicz",{"src":90774},"/images/people/michal-dziadowicz.webp",{"type":725,"value":90776,"toc":90865},[90777,90779,90782,90786,90789,90800,90803,90805,90808,90811,90815,90818,90821,90824,90839,90845,90848,90851,90854,90857,90860,90863],[842,90778,40867],{},[842,90780,90781],{},"Adding GraphQL to DjangoCMS allows exposing data through an api in a structured way. This can be used to build a cms frontend in other technologies or integrate a cms with more applications.",[863,90783,90785],{"id":90784},"installing-packages","Installing packages",[842,90787,90788],{},"Install package ‘graphene-django’ from pip. This will also install:",[958,90790,90791,90794,90797],{},[961,90792,90793],{},"graphene",[961,90795,90796],{},"graphql-core",[961,90798,90799],{},"graphql-relay",[842,90801,90802],{},"Package graphene enables main functionality of graphql and graphene-django allows using django models as nodes in graphql.",[863,90804,2337],{"id":8584},[842,90806,90807],{},"In the settings.py add ‘graphene_django’ to INSTALLED_APPS and add configuration for graphene:",[842,90809,90810],{},"Inside urls.py add",[863,90812,90814],{"id":90813},"adding-a-schema","Adding a schema",[842,90816,90817],{},"Next step is to define a schema. Create a file schema.py and import DjangoObjectType.",[842,90819,90820],{},"The schema variable is the one that is pointed to in the settings file.",[842,90822,90823],{},"Next step is to create an object that can be returned by query. For django models you have to inherit from DjangoObjectType. Class names in graphql usually end with Type on Node. Inside Meta class configure what django model will use. If a model uses parler for translation of fields then for every translated field you have to manually define a type and source for graphene fields.",[842,90825,90826,90827,90830,90831,90834,90835,90838],{},"Next step is to add a function that can populate return types with data. For that you have to add a ",[895,90828,90829],{},"resolve_*"," function inside a Query class. Because the person class defines a Node interface, therefore all object ids will be encoded with base64 in the form ",[895,90832,90833],{},"model:ID",". To get id back use ",[895,90836,90837],{},"from_global_id"," function.",[842,90840,11840,90841,90844],{},[895,90842,90843],{},"localhost/graphql"," interactive console and run:",[842,90846,90847],{},"To query for specific person use:",[842,90849,90850],{},"To add “searching” and “pagination” to people query add two new input parameters for query:",[842,90852,90853],{},"to get the first 5 email addresses.",[842,90855,90856],{},"To add a mutation that can modify an object create a new class:",[842,90858,90859],{},"Create a new mutations class:",[842,90861,90862],{},"Graphene is a great library that can help expose endpoints with a relatively little configuration. Adding new endpoints is simple when you get a grasp of graphene code conventions. This allows rapid development of APIs that expose data in logical and easy to follow ways.",[842,90864,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":90866},[90867,90868,90869],{"id":90784,"depth":729,"text":90785},{"id":8584,"depth":729,"text":2337},{"id":90813,"depth":729,"text":90814},"2021-10-06T00:00:00.000Z","How to add GraphQL to Django CMS using Graphene. Expose data through a structured API with minimal configuration for rapid development.",{"src":90873},"/images/blog/musictechlab_blog_django-cms-and-graphql.webp",{"enabled":738,"items":90875},[90876,90878,90880,90882],{"text":90877,"icon":5365},"Graphene-Django exposes Django models as GraphQL nodes with minimal configuration.",{"text":90879,"icon":1769},"Adding GraphQL to Django CMS enables building frontends in any technology.",{"text":90881,"icon":8737},"DjangoObjectType and resolve functions are the two core building blocks.",{"text":90883,"icon":1067},"Translated fields via Parler require manual field definitions in Graphene.",{},{"title":446,"description":90871},[18784],"WDCE5sMvRuUNeeKPcWJtmg3118Bl7tYkDM6ArLmVZSc",{"id":90889,"title":682,"authors":90890,"badge":723,"body":90893,"category":756,"client":723,"date":90980,"description":90981,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":90982,"keyTakeaways":90984,"meta":90992,"navigation":738,"path":683,"seo":90993,"status":723,"stem":684,"tags":90994,"teaser":723,"__hash__":90995},"posts/blog/software-development/which-framework-should-you-choose-for-the-frontend-web-platform-development.md",[90891],{"name":87508,"avatar":90892},{"src":87797},{"type":725,"value":90894,"toc":90975},[90895,90897,90900,90903,90914,90917,90920,90923,90926,90929,90933,90936,90943,90946,90949,90952,90955,90958,90961,90964,90967,90970,90973],[842,90896,40867],{},[842,90898,90899],{},"In the project development cycle, there is always a step when you finish one project and you are beginning the second one. It usually puts you in front of deciding which tool to use for the implementation of the new frontend application. You will probably have to choose between React, Angular, and Vue. Which one will be the best choice? The answer is: it depends!",[842,90901,90902],{},"Script gives you the possibility to choose within a wide range of frameworks or advanced libraries to build modern applications. These times, the most popular ones are React, Angular, and Vue. Each of them has its advantages and disadvantages, and what is most important, each of them has big support and community. It allows you to find you a solution very fast in case of any problems. All those tools are really great and you can do everything in each of them, but before the final decision, there are several topics that you should think about:",[958,90904,90905,90908,90911],{},[961,90906,90907],{},"project scope,",[961,90909,90910],{},"team experience and learning curve,",[961,90912,90913],{},"project setup and configuration.",[863,90915,87645],{"id":90916},"react",[842,90918,90919],{},"We can easily say React is the most popular JavaScript framework and a definite leader. It allows you to use a reactive approach and a functional programming paradigm. While using React, you use component-based architecture, JSX, and Flux architecture.",[842,90921,90922],{},"React-based apps consist of multiple components. Each component contains JSX markup and business logic. Communication between components can be managed by Flux.",[842,90924,90925],{},"The best advantage of using React is flexibility. React does not impose on you the structure of your project or library you have to use. You are the decision-maker, and you can use whatever you want and arrange your implementation in every way you want. The large community ensures multiple React-based libraries between you can choose to add to your app. React is also quite easy to learn for the newbies.",[842,90927,90928],{},"Paradoxically, flexibility is also the biggest disadvantage of React. During work with React, you have to create your own workflow. You don’t have a built-in solution and a standardized architecture. You have to work it out by yourself. The plenty of given React libraries will make you a headache during the selection of the proper one. You might also waste some time testing the given solutions.",[863,90930,90932],{"id":90931},"angular-2","Angular 2+",[842,90934,90935],{},"Angular 2+ is the newest and improved version of the Angular framework. Angular is a powerful tool with a lot of built-in solutions. It allows you to create a component-based application, which is a big change compared to the previous version of Angular.",[842,90937,90938,90939,90942],{},"With Angular CLI, it’s really easy to create a full configuration of a new application. After a few seconds of installation, you have a project ready to start the implementation. You don’t have to install any other dependencies, you have whatever you need: routing, HTTP client, TypeScript support - just ng new ",[1086,90940,90941],{},"app_name",", and you’re able to write your application without any additional configuration. Angular 2+ forces you a bit to use its project architecture that consists of components, modules, services, providers, pipes, directives, and dependency injection framework. In the beginning, it may annoy you, but when you are more familiar with Angular, you will probably see more sense in this architecture. In the simplest terms, Angular wants you to design frontend apps in a prescribed way, unlike React or Vue.",[842,90944,90945],{},"In Angular, there is also a two-way data binding that allows you to share data between a component class and its template and vice versa. It’s really helpful because sometimes you won’t need to handle, eg., input events to get current input data. After all, it will be handled directly via ngModel. On the other side, it caused some performance problems in the first version of Angular, but in the newest version, it seems to be improved.",[842,90947,90948],{},"Besides the advantages above, Angular is made for applications where there are plenty of forms. Angular’s Reactive Forms module is gorgeous and allows you to create nested and complicated forms with multiple steps and validation support. It seems to be the best solution for forms support these times.",[842,90950,90951],{},"On the other side, Angular is a complex tool, and it’s really hard to learn at the beginning. To start working with Angular, you have to learn each concept of this framework. It’ll also force you to use its worked-out architecture. First touches with Angular may overwhelm you, and you can be discouraged at the very same beginning. After configuring an Angular app, there are some built-in modules installed that you may never use in your app, which is a big redundancy. Also Angular’s state management tool - @ngrx is advanced but too complicated.",[863,90953,90954],{"id":27401},"Vue",[842,90956,90957],{},"Vue is the youngest tool from this list, and it borrows concepts from both React and Angular.",[842,90959,90960],{},"Vue wants to store business logic, HTML markup, and CSS stylesheets in one file, similar to React’s approach. What’s more, Vue uses the props and state objects like in React. Vue also prefers to mix HTML markups with JavaScript, and during work with Vue, you will have to use, eg., v-if or v-bind directives, similarly to Angular. It also supports two-way data binding like in Angular.",[842,90962,90963],{},"Vue has the same flexibility as React. Using Vue enables you to organize your architecture in whatever way you want and you are able to use whatever tools or libs you prefer. It’s also related to the same problems as in React. Vuex, Vue’s state management tool, based on Flux is really cool. It’s simple to learn and use. During work with Vue, you won’t have to learn new things like Angular’s concepts or React’s JSX. It means Vue is the simplest tool and also the easiest to learn. If you’re familiar with React or Angular, switching from those 2 to Vue is a piece of cake.",[842,90965,90966],{},"Unfortunately, Vue is still much less popular than either React or Angular, so it has less ready solutions. Working with Vue may sometimes force you to prepare your own solutions because you won’t find any existing ones. The popularity and community of Vue are growing rapidly from day to day, so it is a matter of time when it will be as popular as React or Angular.",[842,90968,90969],{},"The choice of a proper framework or library for your project should be thoughtful. An approach that you’ll never work with Angular or Vue because you fell in love with React is not appropriate. Each of those tools has its own advantages, and you should consider which one to choose. If you like flexibility or you want to start without learning any additional concepts, take React or Vue. When you need great support for forms management, fast project configuration, or worked-out app architecture, Angular will be the best choice for you.",[842,90971,90972],{},"But what’s most important, please stay a framework agnostic! Each of them is a quality tool, and working with each of them can make you really happy.",[842,90974,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":90976},[90977,90978,90979],{"id":90916,"depth":729,"text":87645},{"id":90931,"depth":729,"text":90932},{"id":27401,"depth":729,"text":90954},"2021-09-20T00:00:00.000Z","Comparing React, Angular, and Vue for frontend web development. Learn which framework fits your project scope, team experience, and configuration needs best.",{"src":90983},"/images/blog/musictechlab_blog_which-framework-should-you-choose-for-the-frontend-web-platform-development.webp",{"enabled":738,"items":90985},[90986,90988,90990],{"text":90987,"icon":5365},"React offers maximum flexibility but requires you to build your own architecture and workflow.",{"text":90989,"icon":8737},"Angular provides built-in routing, HTTP client, and forms but has a steep learning curve.",{"text":90991,"icon":50270},"Vue combines React's flexibility with Angular's two-way binding and is the easiest to learn.",{},{"title":682,"description":90981},[18784],"unaCXVVnvSVp51fsRwzBjfrHlLyq6j19gWgOGXInles",{"id":90997,"title":414,"authors":90998,"badge":723,"body":91001,"category":756,"client":723,"date":91105,"description":91106,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":91107,"keyTakeaways":91109,"meta":91119,"navigation":738,"path":415,"seo":91120,"status":723,"stem":416,"tags":91121,"teaser":723,"__hash__":91122},"posts/blog/software-development/cultural-transformation-through-the-pandemic-era.md",[90999],{"name":90672,"avatar":91000},{"src":90674},{"type":725,"value":91002,"toc":91099},[91003,91005,91011,91014,91017,91020,91024,91027,91030,91033,91055,91059,91062,91066,91069,91072,91075,91078,91086,91092,91097],[842,91004,40867],{},[842,91006,91007,91008],{},"For decades, museums and cultural institutions around the world have been running on conventional structures based around fundraisers, visitors, and exhibitions. However, the Covid 19 pandemic has exerted a major impact on the sector and transformed the way authorities around the world are approaching management. ",[1086,91009,91010],{},"MB1",[842,91012,91013],{},"The Covid 19 pandemic has been specifically detrimental to the global museum and cultural sector. Due to the lockdowns and social restrictions imposed by the pandemic, institutions around the world have struggled to cope with the requirements of this new landscape. According to the UNESCO report, 90% percent of museums worldwide closed temporarily in recent months. This decline represents a significant challenge for leading institutions to adapt based on current conditions.",[842,91015,91016],{},"Technology and digital avenues have emerged to be a key factor in the evolution of the global museum industry. Art and culture institutions have been increasingly referring to technology to convey stories and interact with audiences throughout the lockdown.",[842,91018,91019],{},"This article will explore the steps museums are taking around the world to cope with the impacts of the pandemic and deal with subsequent challenges emerging from the current Covid landscape.",[863,91021,91023],{"id":91022},"transforming-museums-and-galleries-with-technology","Transforming Museums and Galleries with Technology",[842,91025,91026],{},"Even though museums and other institutes had incorporated technology as a facet of their operations before the covid pandemic, the existing landscape was a key factor in changing the emphasis throughout the global museum industry.",[842,91028,91029],{},"Technology is being used as the cornerstone of preserving art and spreading its reach to viewers around the world. This transition is primarily driven by the disruption in conventional working structures and models. Technologies including virtual reality and augmented reality are helping museums tap into newer audiences and blur the boundaries between real and virtual experiences.",[842,91031,91032],{},"Here’s how some of the leading museum authorities from around the world are dealing with the challenges posed by the Covid 19 pandemic.",[958,91034,91035,91043,91052],{},[961,91036,91037,91038,861],{},"Institutions like the Egyptian Tourism Authority are collaborating with studios like VRTEEK to create virtual reality tours, including the ",[846,91039,91042],{"href":91040,"rel":91041},"https://my.matterport.com/show/?m=NeiMEZa9d93&mls=1",[850],"Tour of Pharaoh Ramses",[961,91044,91045,91046,91051],{},"The Museum of Fine Arts Ghent has launched ",[846,91047,91050],{"href":91048,"rel":91049},"https://virtualtour.vaneyck2020.be/en",[850],"360-degree virtual tours"," to cater to digital audiences for the Van Eyck Exhibit in Belgium.",[961,91053,91054],{},"Companies like Google have launched the Google Museums initiatives to provide comprehensive museum tours, including the Van Gogh Museum and other leading cultural landmarks.",[863,91056,91058],{"id":91057},"expanding-the-meaning-of-art","Expanding the Meaning of Art",[842,91060,91061],{},"A key concept behind the transformation of the art landscape has been centered around the redefinition of art. The value of expanding art exposure is being recognized as a much more important factor than the original pieces. Virtual reality and imaging technology are playing a central role in the evolution by expanding art access to millions of new people around the world. The transition has been based around the shift from uniqueness to knowledge as the center point of global museums.",[863,91063,91065],{"id":91064},"virtual-events-and-discussions","Virtual Events and Discussions",[842,91067,91068],{},"Scheduled events and expert discussions are key aspects of leading museums. With the Covid landscape limiting the ability of people to attend physical meetings and events, digital solutions are rapidly becoming the focal point. Donor receptions, cultural events, and fundraisers are increasingly shifting online to cater to the changing visitor dynamics. According to recent research by the PCMA Education Institute, a wide majority of visitors expressed interest in the usage of technology to enhance interactivity with cultural elements in museums. Virtual events and discussions are rapidly becoming the focal point of digital learning and interactions.",[863,91070,91071],{"id":16446},"What’s next",[842,91073,91074],{},"Before the Covid 19 pandemic, museums around the world had their events and structures planned out for years in advance. However, the ever-changing situation during the pandemic has forced museums to adapt to changing information and make critical decisions on the go. The shift to digital channels has been a key aspect of this transformation, with services like Google Museums making a huge leap in transforming arts and culture around the world. The implementation of physical restrictions and lockdowns has been another key factor in propagating the shift towards technologies like Augmented Reality and Virtual Museum Tours. The shift towards technology has been centered around the ability of museums to embrace digital solutions and explore digital platforms to engage with newer audiences.",[842,91076,91077],{},"Bravelab has been at the center of cultural preservation through the creation of innovative commercial solutions for cultural institutes around the world. The company is committed to supporting knowledge conversation and broader sharing through its innovative products and solutions. We are still in development process of few other applications that will help visitors in their experience.",[842,91079,91080,91081],{},"To know more about Bravelab and how the company is changing the digital commerce landscape, explore our ",[846,91082,91085],{"href":91083,"rel":91084},"https://bravelab.io/services",[850],"solutions",[842,91087,91088],{},[846,91089,91090],{"href":91090,"rel":91091},"https://news.artnet.com/art-world/unesco-icom-museums-report-1866558",[850],[842,91093,91094],{},[846,91095,91048],{"href":91048,"rel":91096},[850],[842,91098,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":91100},[91101,91102,91103,91104],{"id":91022,"depth":729,"text":91023},{"id":91057,"depth":729,"text":91058},{"id":91064,"depth":729,"text":91065},{"id":16446,"depth":729,"text":91071},"2021-09-14T00:00:00.000Z","How the shift to digital channels transformed arts and culture during the pandemic, with services like Google Museums leading the way worldwide.",{"src":91108},"/images/blog/musictechlab_blog_cultural-transformation-through-the-pandemic-era.webp",{"enabled":738,"items":91110},[91111,91113,91115,91117],{"text":91112,"icon":3847},"90% of museums worldwide closed temporarily during the pandemic lockdowns.",{"text":91114,"icon":1067},"VR and AR tours let museums reach millions of new digital audiences.",{"text":91116,"icon":5507},"Google Museums launched virtual tours of landmarks like the Van Gogh Museum.",{"text":91118,"icon":4845},"Virtual fundraisers and expert discussions replaced physical events at leading institutions.",{},{"title":414,"description":91106},[74615],"TcHZBvawcouBrIr64rZtPthYh7sPvOVe599C9XbAJTY",{"id":91124,"title":558,"authors":91125,"badge":723,"body":91128,"category":756,"client":723,"date":91212,"description":91213,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":91214,"keyTakeaways":91216,"meta":91224,"navigation":738,"path":559,"seo":91225,"status":723,"stem":560,"tags":91226,"teaser":723,"__hash__":91227},"posts/blog/software-development/is-your-business-ready-for-the-cashless-era.md",[91126],{"name":90672,"avatar":91127},{"src":90674},{"type":725,"value":91129,"toc":91208},[91130,91132,91135,91138,91142,91148,91151,91159,91163,91166,91169,91172,91185,91193,91198,91206],[842,91131,40867],{},[842,91133,91134],{},"Imagine you are time travelling to the year 2035. Standing in front of the counter, you are ready to pay 50 euros. Taking the money out of your pocket, you notice the smile on the cashier's face. All of a sudden, the people behind you start laughing. \"We do not accept physical money anymore,\" the seller says.",[842,91136,91137],{},"You are leaving the shop on the jump. Heading to the city, you stop inside other stores, small talk with strangers, making sure you are not losing your mind. Then it hits you: it is 2035, and physical money does not exist anymore. It is the scenario that day by day becomes more probable.",[863,91139,91141],{"id":91140},"the-advancement-of-technology-is-pushing-cash-into-trash","The advancement of technology is pushing cash into… trash",[842,91143,91144,91145,91147],{},"Throughout the years, various monetary systems kept appearing, facilitating transactions. Today, the trend is accelerating. At the beginning of 2021, digital payments reported a 40% jump in two years ",[1086,91146,4434],{},". According to many, the era of the COVID-19 pandemic additionally reinforced this pattern - e-payments have become a convenient and safe way to finalize purchases. But can they completely replace traditional money?",[842,91149,91150],{},"Over the last dozen years, electronic payment systems have undergone a revolution. Consumers have at their disposal more than just a single PayPal account. We can expect transactions via an online bank account and a payment card, HCE payments, transactions via Visa or MasterPass systems, or cryptocurrency wallets.",[842,91152,91153,91154,91156,91157],{},"In the global context, Europe is the driver of the high growth in digital payments. By 2025, the European market is expected to hit the $1.95trn value. ",[1086,91155,1483],{}," Statistics show that mobile payments in European countries are forecast to surge by 67% by 2023.",[1086,91158,7948],{},[863,91160,91162],{"id":91161},"is-the-fate-of-cash-inevitable","Is the fate of cash inevitable?",[842,91164,91165],{},"The benefits of using electronic payment methods are wide-ranging. First and foremost: convenience. The customer no longer has to carry a wallet that may become a potential target for thieves. All funds are available with a single click on a device screen.",[842,91167,91168],{},"The banks also increasingly choose e-payments. The exchange of electronic money can be easily verified, preventing money laundering attempts and minimizing the fraud risks. What is more, Internet payments get recorded on the current basis - in case of breach of law, it is easier to recover lost property.",[842,91170,91171],{},"Yet, some critics point to the dark side of e-transactions. The transfers of cash onto electronic systems subject the funds to constant control. At the same time, there is a risk of breaching system security: in that case, money and data could be easily appropriated by criminals. Like any payment method, electronic payments have their pros and cons. Who knows, maybe money will soon become just museum exhibits? It is possible but not necessarily bad news for us.",[842,91173,91174,91175,91178,91179],{},"**Bravelab is focused on helping traditional retail companies evolve to the next level with intuitive eCommerce solutions. ",[996,91176,91177],{},"Thanks to our team of skilled developers, we allow companies to implement diverse solutions at their preferred pace, like e-payments integrated with efficient ways for their customers."," Explore how you can sequentially implement eCommerce options for your business, **",[846,91180,91182],{"href":88174,"rel":91181},[850],[996,91183,91184],{},"contact with our specialist.",[842,91186,91187,7826,91189],{},[1086,91188,4434],{},[846,91190,91191],{"href":91191,"rel":91192},"https://www.cpapracticeadvisor.com/accounting-audit/news/21208440/digital-payments-to-hit-66-trillion-in-2021-a-40-jump-in-two-years",[850],[842,91194,91195,91197],{},[1086,91196,1483],{}," Ibid",[842,91199,91200,7826,91202],{},[1086,91201,7948],{},[846,91203,91204],{"href":91204,"rel":91205},"https://it-online.co.za/2021/08/26/mobile-wallet-payments-surge-set-for-more-growth/",[850],[842,91207,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":91209},[91210,91211],{"id":91140,"depth":729,"text":91141},{"id":91161,"depth":729,"text":91162},"2021-09-09T00:00:00.000Z","E-payments have become a convenient and safe way to finalize purchases. Make the next step with your e-commerce e-payments integrated.",{"src":91215},"/images/blog/musictechlab_blog_is-your-business-ready-for-the-cashless-era.webp",{"enabled":738,"items":91217},[91218,91220,91222],{"text":91219,"icon":3920},"Digital payments jumped 40% in two years by early 2021, accelerated by the COVID-19 pandemic.",{"text":91221,"icon":4855},"European mobile payments were forecast to surge 67% by 2023.",{"text":91223,"icon":7495},"E-payments improve convenience and fraud prevention but raise data privacy and security risks.",{},{"title":558,"description":91213},[52276],"YqakGaC1UeijX3o_BvZ0DyFgaeMyGyAEkTFdicoLVmw",{"id":91229,"title":518,"authors":91230,"badge":723,"body":91233,"category":756,"client":723,"date":91388,"description":91389,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":91390,"keyTakeaways":91392,"meta":91400,"navigation":738,"path":519,"seo":91401,"status":723,"stem":520,"tags":91402,"teaser":723,"__hash__":91403},"posts/blog/software-development/how-to-launch-saleor-io-shop-instance-within-40h.md",[91231],{"name":834,"to":720,"avatar":91232},{"src":722},{"type":725,"value":91234,"toc":91382},[91235,91237,91240,91249,91252,91255,91266,91268,91271,91288,91291,91297,91303,91309,91315,91321,91323,91331,91334,91337,91346,91348,91380],[842,91236,40867],{},[863,91238,34087],{"id":91239},"problem",[842,91241,91242,91243,91248],{},"Mag-moto is a niche store for motorcyclists. We have been maintaining our client since 2015. What’s intriguing about it is that the first version of this shop was built with the Saleor monolith software where Saleor had less than 1000 stars on the github (today Salor has more than 13000 stars). During the years between 2015-2021 the shop was working perfectly. Client had a lot of products and orders. Our role was to take care of servers and backups with small improvements from time to time. Then, something bad happened… Our cloud service provider had an accident where millions of websites have lost their data. Irrevocably. (More information from Reuters ",[846,91244,91247],{"href":91245,"rel":91246},"https://www.reuters.com/article/us-france-ovh-fire-idUSKBN2B20NU",[850],"here","). We were one of these OVH clients.",[842,91250,91251],{},"At this point, we needed to face the problem: how fast we are able to restore our client’s shop.",[842,91253,91254],{},"The first thought was to run this project from the backup. However, we had to wait for OVH updates regarding their recovery process. Eventually, we figured out three options:",[958,91256,91257,91260,91263],{},[961,91258,91259],{},"To wait for OVH and run the application from the backup",[961,91261,91262],{},"To move our clients catalog to Shopify instance",[961,91264,91265],{},"To run the store from scratch based on the newest at that time Saleor version (2.1)",[863,91267,27663],{"id":52066},[842,91269,91270],{},"Decision wasn’t so easy because with the first option we didn’t know how much time we would have to wait for the OVH. The second option was not accepted by the client who wanted to have his own shop. Finally, we decided to choose option three. Why? Because:",[958,91272,91273,91276,91279,91282,91285],{},[961,91274,91275],{},"Today's Saleor is a very light and modern software dedicated to developers. It means that is highly customizable",[961,91277,91278],{},"We have experience with a different implementations based on the newest Saleor (2.1)",[961,91280,91281],{},"Client has not expected to rebuild the whole frontend parts. We could use Saleor Storefront out-of-the-box",[961,91283,91284],{},"We had our offline payment plugin which we have coded in a different project and which our client wanted to use",[961,91286,91287],{},"It was a big chance to replace the legacy software with the newest version",[842,91289,91290],{},"After a small brainstorming the final decision was made. Based on our previous implementations we knew that default setup is not so hard to do. We assumed that we would have five days to build this project from scratch. The plan was to:",[842,91292,91293,91296],{},[996,91294,91295],{},"Day 1:"," Setup a new VPS environment and Saleor default implementation",[842,91298,91299,91302],{},[996,91300,91301],{},"Day 2:"," Give access for the client to administration panel (Client was able to learn a new Saleor)",[842,91304,91305,91308],{},[996,91306,91307],{},"Day 3:"," Add necessary customizations and configurations (Stripe, Offline payment, Mail, Monitoring)",[842,91310,91311,91314],{},[996,91312,91313],{},"Day 4:"," Tweak storefront with small improvements (mainly on checkout page)",[842,91316,91317,91320],{},[996,91318,91319],{},"Day 5:"," Assembly everything together and launch on client’s main domain",[863,91322,87906],{"id":1473},[842,91324,91325,91326],{},"Since June 2021 Mag-moto store has been working perfectly. Client is really happy with our delivery. Why? Read more here: ",[846,91327,91330],{"href":91328,"rel":91329},"https://clutch.co/profile/musictech-lab#review-1788005",[850],"Clutch.co",[842,91332,91333],{},"Our full-stack development team has spent 40h on this project. Half of this time was to tweak some shop parts and behavior (e.g. offline payment) and testing, as well.",[842,91335,91336],{},"Summarizing: We’ve learned one important lesson: As a professional service company we want to give our customers the best options and help them choose. Last thing is that thanks to our approach and cooperation with the client we turned around the problem into success which is the best thing that we could do.",[842,91338,91339,91340,91345],{},"If you’re not sure that Saleor is a Software for you, ",[846,91341,91344],{"href":91342,"rel":91343},"https://calendly.com/daniel-sasiadek",[850],"call us and get a consultation"," which helps you to make correct decisions.",[863,91347,72132],{"id":85315},[958,91349,91350,91356,91362,91368,91371,91374],{},[961,91351,91352,91355],{},[996,91353,91354],{},"How many hours did you spend on this project?"," Up to 40h",[961,91357,91358,91361],{},[996,91359,91360],{},"How many developers did you require?"," A one full-stack developer",[961,91363,91364,91367],{},[996,91365,91366],{},"What is the cost of a VPS server for this implementation?"," Around $ 20 USD monthly",[961,91369,91370],{},"**What is a server specification? **Minimum 4GB RAM, 2 processors, 80GB SSD, Ubuntu LTS",[961,91372,91373],{},"**How much time did you spend to write a custom plugin? **Up to 8h",[961,91375,91376,91379],{},[996,91377,91378],{},"What was the biggest challenge you faced?"," Saleor Storefront modifications (we’re building our own storefront based on Vue3 and Tailwind)",[842,91381,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":91383},[91384,91385,91386,91387],{"id":91239,"depth":729,"text":34087},{"id":52066,"depth":729,"text":27663},{"id":1473,"depth":729,"text":87906},{"id":85315,"depth":729,"text":72132},"2021-09-08T00:00:00.000Z","How we rebuilt a Saleor.io e-commerce store from scratch in 40 hours after a cloud provider disaster, covering deployment, data migration, and lessons learned.",{"src":91391},"/images/blog/musictechlab_blog_how-to-launch-saleor-io-shop-instance-within-40h.webp",{"enabled":738,"items":91393},[91394,91396,91398],{"text":91395,"icon":3271},"A full-stack developer rebuilt a Saleor e-commerce store from scratch in just 40 hours.",{"text":91397,"icon":3847},"OVH cloud disaster destroyed millions of sites; offline backups were not available in time.",{"text":91399,"icon":37696},"Choosing the latest Saleor version turned a crisis into a successful legacy-to-modern migration.",{},{"title":518,"description":91389},[52276,18784],"vtCfoEYQ9uJ24lIlKxoG2t-2_pLZ42ZgMr2Eg0yZEFE",{"id":91405,"title":562,"authors":91406,"badge":723,"body":91409,"category":756,"client":723,"date":91556,"description":91557,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":91558,"keyTakeaways":91560,"meta":91568,"navigation":738,"path":563,"seo":91569,"status":723,"stem":564,"tags":91570,"teaser":723,"__hash__":91571},"posts/blog/software-development/is-your-face-ready-to-buy.md",[91407],{"name":90672,"avatar":91408},{"src":90674},{"type":725,"value":91410,"toc":91548},[91411,91413,91416,91419,91422,91426,91429,91432,91435,91445,91448,91451,91454,91489,91493,91496,91505,91509,91517,91520,91524,91527,91530,91533,91535,91541,91546],[842,91412,40867],{},[842,91414,91415],{},"The evolution of technology is leading to the curation of new customer engagement mediums. As brands strive to have a deeper understanding of customer behaviours, technologies like facial recognition are actively considered the most viable avenue to enhance existing knowledge.",[842,91417,91418],{},"Facial recognition technology has been at the epicentre of technological innovation in areas of security and management around the world. Companies have been using facial recognition to improve security assessment in critical infrastructure. However, with the evolution of technology, new applications are being explored across the eCommerce domain.",[842,91420,91421],{},"This article will explore the growth of facial recognition technology and how it is transforming global eCommerce experiences.",[863,91423,91425],{"id":91424},"deeper-behavioral-understanding-with-facial-recognition","Deeper Behavioral Understanding with Facial Recognition",[842,91427,91428],{},"Even though facial recognition technology has long been touted as the optimal solution in areas like security, anti-terrorism, and healthcare, the practical deployment of the technology leads to the exploration of a much broader perspective.",[842,91430,91431],{},"Countries like China have been transforming customer eCommerce experiences by integrating advanced facial recognition systems in stores to evaluate customer experiences. The country's usage of facial recognition systems has been actively increasing from areas like security and surveillance to broader aspects, including eCommerce.",[842,91433,91434],{},"With facial recognition being an emerging domain, leading global companies currently are actively using the technology for security and surveillance procedures. The applicability of the technology has led to a steep decline in criminal activity in major cities around the world.",[863,91436,91438,91439,91444],{"id":91437},"according-to-a-recent-report-by-the-carnegie-endowment-for-international-peace-over-75-global-countries-use-ai-based-facial-recognition-solutions-for-active-surveillance","According to a recent report by the",[846,91440,91443],{"href":91441,"rel":91442},"https://carnegieendowment.org/",[850]," Carnegie Endowment for International Peace",", over 75 global countries use AI-based facial recognition solutions for active surveillance",[842,91446,91447],{},"However, the expansion of research in the domain has led to the development of wider use-cases of facial recognition in emotion recognition and broader customer-oriented territories.",[842,91449,91450],{},"The introduction of facial recognition into eCommerce and digital commerce avenues offers promising prospects due to the possibilities offered by the technology. eCommerce stores are being optimized to cater to feedback from facial recognition technology deployed within stores. Customer feedback towards test products is being measured through facial recognition technology, leading to intuitive e-store layouts and brilliant product selection in stores. The integration of customer feedback into product inventories enables stores to curate their galleries based on customer preferences.",[842,91452,91453],{},"Here are some ways companies are exploring the implementation of facial recognition technology to enhance customer interactions.",[958,91455,91456,91467,91478],{},[961,91457,91458,91459,91466],{},"Leading companies like ",[846,91460,91463,7826],{"href":91461,"rel":91462},"https://www.amazon.com/-/es/b/ref=s9_acss_bw_cg_agogo_2b1_w?node=20931384011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=merchandised-search-20&pf_rd_r=3S0XA6HBKZ6RHAGTS27S&pf_rd_t=101&pf_rd_p=d510bfd7-cb66-42fa-820f-51e50a76df14&pf_rd_i=16008589011",[850],[996,91464,91465],{},"Amazon","have launched Amazon Go stores with automated facial recognition-based payments to automate the shopping experience for customers.",[961,91468,91469,91470,91477],{},"Chinese Coffee Brands like ",[846,91471,91474],{"href":91472,"rel":91473},"https://www.datatrekresearch.com/coffee-tea-or-ai-powered-facial-recognition/",[850],[996,91475,91476],{},"Luckin","** **are introducing AI-based facial recognition solutions to provide intuitive customer recommendations.",[961,91479,91480,91481,91488],{},"Brands like ",[846,91482,91485],{"href":91483,"rel":91484},"https://www.globaltimes.cn/page/202103/1217212.shtml",[850],[996,91486,91487],{},"EmoKit Tech","** **are developing commercial applications of emotion-recognition software to create next-generation customer experiences.",[863,91490,91492],{"id":91491},"payments-through-facial-recognition","Payments Through Facial Recognition",[842,91494,91495],{},"With the adoption of the Face ID by Apple, facial recognition is being actively deployed as a means of customer security worldwide. The applicability of facial recognition-based payment systems can enhance customer payment experiences and increase payment security.",[842,91497,91498,91499,91504],{},"Leading technological giants, including",[846,91500,91503],{"href":91501,"rel":91502},"https://aws.amazon.com/blogs/machine-learning/build-your-own-face-recognition-service-using-amazon-rekognition/?",[850]," Amazon"," and Alibaba, have been enhancing the usage of facial recognition across consumer products by allowing customers to make payments through facial recognition.",[863,91506,91508],{"id":91507},"improved-customer-service","Improved Customer Service",[842,91510,91511,91512,91516],{},"Facial recognition can be a great tool to analyze customer facial expressions to evaluate emotions. Companies can introduce an improved level of marketing by having a better assessment of user responses to their products. These responses are a great way of evaluating customer reactions to test products and services within existing stores. In countries like China, coffee shops actively deploy",[846,91513,91515],{"href":91472,"rel":91514},[850]," facial recognition"," technology to improve customer engagement through personalized recommendations.",[842,91518,91519],{},"Evaluating customer sentiments during shopping can allow businesses to enhance their product experience and tailor it around customer expectations. Hence, Facial recognition technology is proving to be the cornerstone of next-generation customer interaction thanks to real-time data.",[863,91521,91523],{"id":91522},"we-can-do-it-too","We can do it too",[842,91525,91526],{},"Facial recognition is emerging as a transformative force in eCommerce management across the world. The sector is witnessing innovative measures ranging from facial recognition embedded payments to broader sentiment analysis. The improvement in facial recognition technology is backed by interest from leading global companies, including Apple and Amazon. Countries like China have also been at the forefront of practical implementation of the technology in eCommerce avenues. The deployment of these technologies in modern settings can enhance customer experience and improve transaction security.",[842,91528,91529],{},"Bravelab is at the forefront of next-generation eCommerce solutions to support companies with innovative business solutions. Backed by a diverse team of qualified developers, the company optimizes workflow through advanced technologies and eCommerce solutions.",[842,91531,91532],{},"To know more about Bravelab, explore our website and learn more about how you can enhance eCommerce experiences for your business.",[842,91534,5409],{},[842,91536,91537,38405],{},[846,91538,91539],{"href":91539,"rel":91540},"https://aws.amazon.com/blogs/machine-learning/build-your-own-face-recognition-service-using-amazon-rekognition/",[850],[842,91542,91543],{},[846,91544,91472],{"href":91472,"rel":91545},[850],[842,91547,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":91549},[91550,91551,91553,91554,91555],{"id":91424,"depth":729,"text":91425},{"id":91437,"depth":729,"text":91552},"According to a recent report by the Carnegie Endowment for International Peace, over 75 global countries use AI-based facial recognition solutions for active surveillance",{"id":91491,"depth":729,"text":91492},{"id":91507,"depth":729,"text":91508},{"id":91522,"depth":729,"text":91523},"2021-09-06T00:00:00.000Z","How facial recognition technology is transforming e-commerce, from payments at Amazon and Alibaba to customer behavior analysis in retail stores.",{"src":91559},"/images/blog/musictechlab_blog_is-your-face-ready-to-buy.webp",{"enabled":738,"items":91561},[91562,91564,91566],{"text":91563,"icon":11614},"Over 75 countries use AI-based facial recognition for surveillance, per Carnegie Endowment.",{"text":91565,"icon":5504},"Amazon Go and Alibaba already deploy facial recognition for automated store payments.",{"text":91567,"icon":12409},"Emotion recognition from facial data lets stores personalize product recommendations in real time.",{},{"title":562,"description":91557},[14100,52276],"D--BXfZcBp_kVE9LqMOR1Tlq-pDoJYHV0Cqoch2q5d4",{"id":91573,"title":454,"authors":91574,"badge":723,"body":91579,"category":756,"client":723,"date":91619,"description":91620,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":91621,"keyTakeaways":91623,"meta":91631,"navigation":738,"path":455,"seo":91632,"status":723,"stem":456,"tags":91633,"teaser":723,"__hash__":91634},"posts/blog/software-development/establishing-cooperation-between-netlify-and-bravelab.md",[91575],{"name":91576,"avatar":91577},"Daniel Sąsiadek",{"src":91578},"/images/people/daniel-sasiadek.webp",{"type":725,"value":91580,"toc":91616},[91581,91583,91586,91589,91592,91595,91599,91602,91605,91608,91611,91614],[842,91582,40867],{},[842,91584,91585],{},"Nowadays partnership has become a great deal for the companies, we believe that it is the thing also for Bravelab.io. We would like to share with you a short article about our partnership relation with team Netlify and how it all started.",[842,91587,91588],{},"For those who don't know Netlify, it is a platform for deploying your sites on the internet. It was founded by Matt Biilmann in the year 2014 with the purpose to make simple site publishing. Since then it has been very rapidly growing and today Netlify serves over 10 000 sites making $1m+ a month and working with great clients around the globe in various industries.",[842,91590,91591],{},"Moreover, Netlify offers a very interesting plugin for VSCode where you can create, edit and deploy your static websites and applications.",[842,91593,91594],{},"The thing about partner programs is that you can collaborate with companies in different fields in order to reach new markets or make business together. In our case, it was about one of the most useful tools for developers and our clients.",[863,91596,91598],{"id":91597},"why-did-we-decide-to-serve-the-netlify-plugin","Why did we decide to serve the Netlify plugin?",[842,91600,91601],{},"We used this tool for our marketing websites and there were no reasons not to use it for our development. It's really very simple and easy to operate. Now, in order to have this powerful tool we had to - obviously - set up a new account with Netlify but that was nothing on the list of things we needed from them.",[842,91603,91604],{},"The first step was to make a standard partnership package between Netlify and Bravelab.io. We were discussing the terms and we quickly agreed that the best way to proceed on this matter was to get a free account from them. Yay, we got a Netlify account and started trying out features.",[842,91606,91607],{},"And here is one great thing - you can test their service for 30 days for free! So, we did it. And in that time we took advantage of Netlify Deployments feature as well as CDN connection at BitBalloon. Yes, it's absolutely free of charge with this partnership program.",[842,91609,91610],{},"We realized that the Products team at Netlify are very helpful and easy-going people so there was no time lost for establishing the relations with each other.",[842,91612,91613],{},"We are really happy to work with their Team and we see many advantages with that cooperation, also we see that our development crew is working faster on the projects. From the clients standpoint we see that live preview of the changes made after every build is helping with better communication between the client and our development team.",[842,91615,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":91617},[91618],{"id":91597,"depth":729,"text":91598},"2021-08-27T00:00:00.000Z","The partnership has become a great deal for the companies, we believe that it is the thing also for Bravelab.io.",{"src":91622},"/images/blog/musictechlab_blog_establishing-cooperation-between-netlify-and-musictechlab.webp",{"enabled":738,"items":91624},[91625,91627,91629],{"text":91626,"icon":1067},"Netlify serves over 10,000 sites and generates $1M+ per month.",{"text":91628,"icon":11617},"Partnership included a free account with deployments and CDN access.",{"text":91630,"icon":37696},"Live preview after every build improved client-developer communication.",{},{"title":454,"description":91620},[15279,74615],"Ep-hP7DmHhHF5hXpUTZ2KYCqnvY0ira6_lUBiVfMU_g",{"id":91636,"title":566,"authors":91637,"badge":723,"body":91640,"category":756,"client":723,"date":91906,"description":91907,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":91908,"keyTakeaways":91910,"meta":91918,"navigation":738,"path":567,"seo":91919,"status":723,"stem":568,"tags":91920,"teaser":723,"__hash__":91921},"posts/blog/software-development/javascript-trending-frameworks-and-market-opportunities.md",[91638],{"name":90672,"avatar":91639},{"src":90674},{"type":725,"value":91641,"toc":91895},[91642,91644,91647,91651,91654,91656,91659,91662,91664,91667,91687,91691,91697,91702,91728,91733,91755,91760,91783,91789,91794,91815,91820,91843,91849,91854,91876,91882,91885,91893],[842,91643,40867],{},[842,91645,91646],{},"According to a survey made by Stack Overflow, JavaScript is considered the most famous programming language among developers for eight years in a row. It is used in both front-end and back-end applications. Therefore, It is popular among developers. That's the reason most huge companies are currently using JavaScript Frameworks.",[863,91648,91650],{"id":91649},"what-is-a-framework","What is a Framework",[842,91652,91653],{},"Let's start by a simple example to explain the concept of a Framework. You have a group of bricks. These bricks are the Framework. They can be used to build a house. The bricks have certain dimensions that you have to work with. You can build houses of different sizes and shapes but you can't manufacture a car or an airplane with it. You need to search for a different framework for that. JavaScript Framework provides a certain set of rules that can be used by any developer to make complex applications faster and efficient. It is easier than building an application from scratch. JavaScript is used for both client and server side so there are multiple frameworks to work with.",[863,91655],{"id":728},[842,91657,91658],{},"What is the difference between Framework and a Library",[842,91660,91661],{},"Framework is a full toolset that helps organize your website or application, while on the other side library is a collection of code functions that provide needed library of features and don't help in shaping your application. The Three main types of Frameworks in JavaScript are: Front-end Frameworks, Back-end Frameworks and Testing Frameworks.",[863,91663],{"id":76849},[842,91665,91666],{},"Difference between frontend, backend and testing",[958,91668,91669,91675,91681],{},[961,91670,91671,91674],{},[996,91672,91673],{},"Front-end:"," It is the programming which focuses mainly on the client side (visual elements of a website that a user will interact with).",[961,91676,91677,91680],{},[996,91678,91679],{},"Back-end:"," It is the programming which focuses mainly on the server side (side of a website users can't see). Front-end and Back-end work together to create a dynamic website that allows users to apply any interactive activities you might take part in while using the website.",[961,91682,91683,91686],{},[996,91684,91685],{},"Testing:"," As the name says, testing is used to ensure the functionality correctness of the website.",[863,91688,91690],{"id":91689},"discussion-of-most-popular-javascript-frameworks","Discussion of most popular JavaScript Frameworks",[1074,91692,91694],{"id":91693},"front-end-frameworks",[996,91695,91696],{},"Front-end Frameworks",[842,91698,91699],{},[996,91700,91701],{},"REACT",[958,91703,91704,91710,91716,91722],{},[961,91705,91706,91709],{},[996,91707,91708],{},"Developer:"," It is developed by Facebook.",[961,91711,91712,91715],{},[996,91713,91714],{},"Functionality:"," It helps in building interactive user interfaces. In addition, it provides a component system where code is written once and can be reused in different parts.",[961,91717,91718,91721],{},[996,91719,91720],{},"In comparison to other Frameworks:"," It is considered number one in terms of some of the most important factors, like developer satisfaction, interest, usage, stability, and popularity.",[961,91723,91724,91727],{},[996,91725,91726],{},"Popular companies that are built using React:"," Twitter, Airbnb, New Your Times and Instagram.",[842,91729,91730],{},[996,91731,91732],{},"ANGULAR",[958,91734,91735,91738,91743,91749],{},[961,91736,91737],{},"**Developer: **It is developed by Google.",[961,91739,91740,91742],{},[996,91741,91714],{}," It is more than just a framework but a platform used for building dynamic web applications. It has all the functionalities and features that can be needed by a front-end application.",[961,91744,91745,91748],{},[996,91746,91747],{},"In Comparison to other Frameworks:"," It was popular with React at the beginning but popularity decreased over years due to lack of flexibility and requirement to be familiar with two programming languages. As angular applications are written in Typescript.",[961,91750,91751,91754],{},[996,91752,91753],{},"Popular companies that are built using Angular:"," Guardian, IBM, Udacity and YouTube.",[842,91756,91757],{},[996,91758,91759],{},"VUE",[958,91761,91762,91767,91772,91777],{},[961,91763,91764,91766],{},[996,91765,91708],{}," It is developed by Evan You, an ex-Google employee. Currently it is maintained by a group of core team members and open source collaborators.",[961,91768,91769,91771],{},[996,91770,91714],{}," It is more flexible. It can be used as a plug-and-play library. Also, It has a lot of helper tools that can be used for creating efficient, fast and progressive single page Application.",[961,91773,91774,91776],{},[996,91775,91747],{}," It is ranked among the top 3 Frameworks. This is due to its simplicity, small size and it is much easier to set up. That's the reason it outraged the performance of a lot of competitors.",[961,91778,91779,91782],{},[996,91780,91781],{},"Popular companies that are built using Vue:"," a lot of applications for the following companies: Apple, 9GAG, Gitlab and Dribble.",[1074,91784,91786],{"id":91785},"back-end-frameworks",[996,91787,91788],{},"Back-end Frameworks",[842,91790,91791],{},[996,91792,91793],{},"EXPRESS",[958,91795,91796,91799,91804,91809],{},[961,91797,91798],{},"**Developer: **It is developed by the Node foundation.",[961,91800,91801,91803],{},[996,91802,91714],{}," It is famous for its small size, performance and high speed in getting HTTP server up and running. It provides a neat, easy and quick way, abstracted API over the more complex Node.js framework.",[961,91805,91806,91808],{},[996,91807,91747],{}," It is the most preferred JS framework until recently especially for developers who want to use JavaScript for both their front and back-end stacks. It is beginner-friendly and enables developers to build complex applications.",[961,91810,91811,91814],{},[996,91812,91813],{},"Popular companies that use Express:"," IBM, Twitter and PayPal.",[842,91816,91817],{},[996,91818,91819],{},"NEXT",[958,91821,91822,91827,91832,91837],{},[961,91823,91824,91826],{},[996,91825,91708],{}," It is a React-based framework .",[961,91828,91829,91831],{},[996,91830,91714],{}," It builds pre-rendered React web applications for the client. Pre-rendering is important to increase the SEO performance of a website. Also, it increases website visibility to different search engines.",[961,91833,91834,91836],{},[996,91835,91747],{}," According to the statistics, it is currently the most popular Backend framework. It has zero up-front required configuration. This takes care of most of the optimizations, giving chances for developers to focus on writing the code and framework is responsible for rending.",[961,91838,91839,91842],{},[996,91840,91841],{},"Popular companies that use Next:"," Uber, Netflix, Starbucks, Tiktok and Nike.",[1074,91844,91846],{"id":91845},"testing-frameworks",[996,91847,91848],{},"Testing Frameworks",[842,91850,91851],{},[996,91852,91853],{},"JEST",[958,91855,91856,91860,91865,91870],{},[961,91857,91858,91709],{},[996,91859,91708],{},[961,91861,91862,91864],{},[996,91863,91714],{}," It makes sure of JS code accuracy and correctness through an easy-to-use API for building applications.",[961,91866,91867,91869],{},[996,91868,91747],{}," It is one of the most popular Testing Frameworks. It has a complete testing framework with several features like built-in code coverage, snapshot testing and a lot of parameters under a single Framework.",[961,91871,91872,91875],{},[996,91873,91874],{},"Popular companies that use Jest:"," Airbnb, Facebook and Instagram.",[863,91877,91879],{"id":91878},"javascript-bravelab",[996,91880,91881],{},"JavaScript + Bravelab",[842,91883,91884],{},"Developing a website or various applications can be a tedious task even with all the new frameworks that are introduced in JavaScript. There are multiple decisions that need to be made for example but not limited to choice of the Framework that suits your need the most on both front-end and back-end applications.",[842,91886,91887,7826,91890],{},[846,91888,88298],{"href":88520,"rel":91889},[850],[964,91891,91892],{},"saved the hustle by getting your application done in a few easy milestones under the supervision of a team of the top talented engineers in the market. Let us give the opportunity to develop your next big platform with JavaScript.",[842,91894,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":91896},[91897,91898,91899,91900,91905],{"id":91649,"depth":729,"text":91650},{"id":728,"depth":729,"text":728},{"id":76849,"depth":729,"text":728},{"id":91689,"depth":729,"text":91690,"children":91901},[91902,91903,91904],{"id":91693,"depth":1112,"text":91696},{"id":91785,"depth":1112,"text":91788},{"id":91845,"depth":1112,"text":91848},{"id":91878,"depth":729,"text":91881},"2021-08-23T00:00:00.000Z","An overview of trending JavaScript frameworks for frontend, backend, and testing. Learn which JS tools can make your applications faster and more efficient.",{"src":91909},"/images/blog/musictechlab_blog_javascript-trending-frameworks-and-market-opportunities.webp",{"enabled":738,"items":91911},[91912,91914,91916],{"text":91913,"icon":3920},"JavaScript has been the most popular programming language for 8 consecutive years per Stack Overflow.",{"text":91915,"icon":5365},"React leads in developer satisfaction; Vue wins on simplicity; Angular excels at enterprise apps.",{"text":91917,"icon":37696},"Next.js is currently the most popular backend JS framework thanks to zero-config pre-rendering.",{},{"title":566,"description":91907},[18784],"syKnzoRGGTFnD2adMp0NFG88zfhfQBvoeOMr9q20Lyg",{"id":91923,"title":386,"authors":91924,"badge":723,"body":91927,"category":756,"client":723,"date":91988,"description":91989,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":91990,"keyTakeaways":91992,"meta":92000,"navigation":738,"path":387,"seo":92001,"status":723,"stem":388,"tags":92002,"teaser":723,"__hash__":92003},"posts/blog/software-development/braveloper.md",[91925],{"name":90672,"avatar":91926},{"src":90674},{"type":725,"value":91928,"toc":91982},[91929,91931,91935,91938,91941,91944,91946,91949,91952,91956,91959,91962,91966,91969,91980],[842,91930,40867],{},[1074,91932,91934],{"id":91933},"definition","Definition",[842,91936,91937],{},"In the programming universe is defined as a hero in Python, he masters the deepest and vast world of Python. His knowledge allows him to create amazing platforms from this world. And his main characteristic is that she/he works with enthusiasm in the Brave’s spaceship with its crew.",[842,91939,91940],{},"Would you like to join us as Braveloper?",[842,91942,91943],{},"Learn now what’s inside the Brave’s spaceship and its crew:",[863,91945],{"id":728},[842,91947,91948],{},"Profit Sharing",[842,91950,91951],{},"Profit sharing is a simple system that is based on dividing X percent of company's net profit among all employees in the company. The amount should be equal for everyone who has worked through the whole quarter. Seniority doesn’t matter. Everyone gets the same amount of money. Every Braveloper gets a part of the quarter profit as part of bonus.",[863,91953,91955],{"id":91954},"productivity-with-kanban","Productivity with Kanban",[842,91957,91958],{},"Kanban board methodology is a visual productivity workflow tool that was first used by Toyota in the 1950s. It gives users an overview of an entire task, allowing them to see the details without losing track of the bigger picture. The Kanban method breaks tasks down into 3 categories: “To Do,” “Doing” and “Done,” using cards to plot a project through from start to finish, enabling your team to chart their progress as they work.",[842,91960,91961],{},"At Bravelab we love productivity and results, that’s why we have integrated the methodology of Kanban in our processes, work teams, and workflow. We use software made by Atlassian and called Jira. There we gather all projects and tasks to do, classify them by department, and assign them to every member of the team according to our needs. This helps us to manage and track the projects with high efficiency and most importantly, we deliver top results on time to every client. Every Braveloper is trained to use this powerful tool from the beginning!",[863,91963,91965],{"id":91964},"fresh-ideas-with-a-democratic-leadership","Fresh ideas with a democratic leadership",[842,91967,91968],{},"At Bravelab we always love to hear our colleagues and partners to foster innovation and reach every goal we set. That’s why we follow a philosophy of partnership and transparency that built a democratic leadership inside the “spaceship”. As one references states, “the democratic leadership style shows up in studies again and again as a strategy that inspires and motivates employees. This style allows people to experience a sense of control over their own destiny within the organization and to believe their actions impact the company’s greater success.”",[842,91970,91971,91972,91979],{},"We invite you to ",[846,91973,91976],{"href":91974,"rel":91975},"https://bravelab.recruitee.com/o/remote-python-developer",[850],[996,91977,91978],{},"join us"," and begin the great journey inside the programming universe!",[842,91981,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":91983},[91984,91985,91986,91987],{"id":91933,"depth":1112,"text":91934},{"id":728,"depth":729,"text":728},{"id":91954,"depth":729,"text":91955},{"id":91964,"depth":729,"text":91965},"2021-08-06T00:00:00.000Z","Meet the Braveloper: our definition of a brave developer who masters Python and builds amazing platforms. Discover what it takes to join our crew.",{"src":91991},"/images/blog/musictechlab_blog_braveloper.webp",{"enabled":738,"items":91993},[91994,91996,91998],{"text":91995,"icon":1774},"Every employee gets equal quarterly profit sharing regardless of seniority.",{"text":91997,"icon":85476},"Kanban with Jira tracks all projects from start to delivery.",{"text":91999,"icon":4845},"Democratic leadership fosters innovation and gives every team member a voice.",{},{"title":386,"description":91989},[74615],"DxEtw0SwoWkucZE_7U0yFeYjdVF4mUkgH_t_JuW1OSk",{"id":92005,"title":690,"authors":92006,"badge":723,"body":92009,"category":756,"client":723,"date":92246,"description":92247,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":92248,"keyTakeaways":92250,"meta":92258,"navigation":738,"path":691,"seo":92259,"status":723,"stem":692,"tags":92260,"teaser":723,"__hash__":92261},"posts/blog/software-development/why-the-programming-world-loves-python.md",[92007],{"name":90672,"avatar":92008},{"src":90674},{"type":725,"value":92010,"toc":92240},[92011,92020,92040,92044,92047,92051,92054,92106,92110,92118,92158,92161,92164,92168,92171,92229,92232],[842,92012,92013,92014,92019],{},"According to the TIOBE Index, ",[846,92015,92018],{"href":92016,"rel":92017},"https://www.tiobe.com/tiobe-index/python/",[850],"Python was awarded Language of The Year in 2020"," and remains one of the most popular languages worldwide. No prerequisites needed — logical thinking and a project idea are enough to get started.",[1045,92021,92023,92028,92032,92036],{"className":92022},[1048,1049,1765,1051,1052],[1054,92024],{"description":92025,"icon":92026,"title":92027},"Simple syntax, readable code, and a gentle learning curve make Python ideal for beginners.","i-lucide-graduation-cap","Easy to Learn",[1054,92029],{"description":92030,"icon":12412,"title":92031},"Thousands of libraries and frameworks built by one of the largest developer communities.","Massive Ecosystem",[1054,92033],{"description":92034,"icon":8737,"title":92035},"Used in backend, AI, data science, automation, web development, and more.","Versatile",[1054,92037],{"description":92038,"icon":2939,"title":92039},"Battle-tested in production by companies like Google, Netflix, and Spotify.","Fast & Reliable",[863,92041,92043],{"id":92042},"why-is-everyone-talking-about-python","Why Is Everyone Talking about Python?",[842,92045,92046],{},"Python works for everything — from automating manual tasks to building production backends. Its simple syntax and huge community mean you can find a library for almost any problem.",[863,92048,92050],{"id":92049},"crash-courses-to-learn-python","Crash Courses to Learn Python",[842,92052,92053],{},"We live in exciting times, where there are plenty of resources available through the internet. From free courses to more specialized ones, it is possible to learn Python from zero nowadays. If it is your first language and you are improving your coding skills, here is a list of courses ranging from free to US$ 240.",[958,92055,92056,92066,92076,92086,92096],{},[961,92057,92058,92065],{},[846,92059,92062],{"href":92060,"rel":92061},"https://www.udemy.com/course/complete-python-bootcamp/",[850],[996,92063,92064],{},"2021 Complete Python Bootcamp From Zero to Hero in Python",": A great place to start, this course has more than 250,000 students and it is the most popular Python course on Udemy. You will have all the knowledge necessary to code in Python 3, from setting up the machine, practical homework and quizzes, to building your portfolio with three projects. The best part? You can get it for around $10 in Udemy Flash sales.",[961,92067,92068,92075],{},[846,92069,92072],{"href":92070,"rel":92071},"https://www.coursera.org/specializations/python",[850],[996,92073,92074],{},"Python for Everybody Specialization",": If you are looking for a good theory and some projects, from world-class universities, with certification, in the end, this is the best choice. Offered by the University of Michigan, you will learn Web Development, Data Structure, and Database access using Python. Audit it for free or pay around $40 per month for the full experience.",[961,92077,92078,92085],{},[846,92079,92082],{"href":92080,"rel":92081},"https://www.codecademy.com/learn/learn-python-3",[850],[996,92083,92084],{},"Learn Python 3",": With membership starting at around $15.99 per month, CodeAcademy offers several courses, and this Python course is an interactive option if you don't want to be stuck in theory for so long. With 25 hours of content, you will get a solid base on Python 3.",[961,92087,92088,92095],{},[846,92089,92092],{"href":92090,"rel":92091},"https://onemonth.com/courses/python",[850],[996,92093,92094],{},"Learn Python OneMonth",": With four real-world projects and more than six hours of step-by-step video tutorials, you will learn in four weeks how to create scripts, use APIs and build your web development skills. With an annual plan, you can learn Python and several courses for 12 months subscribing for US$ 240.",[961,92097,92098,92105],{},[846,92099,92102],{"href":92100,"rel":92101},"https://www.pluralsight.com/courses/python-fundamentals",[850],[996,92103,92104],{},"Python Fundamentals",": With the subscriptions method, Pluralsight offers many good courses, and this Python course will teach all the fundamentals you need. With five hours, of course, learn the basics from the language and how to ship and maintain your code. The subscription plans start with US$ 29 monthly and US$ 199 annually.",[863,92107,92109],{"id":92108},"job-opportunities-for-python-programmers","Job Opportunities for Python Programmers",[842,92111,92112,92113],{},"One of the reasons Python is so popular is that it is very flexible. You can use it in back-end and front-end applications, software engineering, databases, web development, and artificial intelligence! Take a look at the top positions and their salaries, according to ",[846,92114,92117],{"href":92115,"rel":92116},"https://insights.stackoverflow.com/survey/2020",[850],"Stack Overflow Survey.",[958,92119,92120,92126,92132,92138],{},[961,92121,92122,92125],{},[996,92123,92124],{},"Data Engineer (US$ 65k):"," One of the most important roles in the industry today uses Python every day. Python is the main language to help Data Engineers to create databases, data pipelines, and ETL processes.",[961,92127,92128,92131],{},[996,92129,92130],{},"Data Scientist (US$ 58k):"," Python can be used in the whole spectrum of a Data Scientist job. From exploring the data, cleaning it, modeling with AI algorithms, creating scripts to uncover insights, and showing them with visualization tools.",[961,92133,92134,92137],{},[996,92135,92136],{},"Machine Learning Engineer (US$ 58k):"," Create, manage, deploy and maintain Machine Learning models using Python. Apply your skills to create functions and access cloud APIs to put AI in production.",[961,92139,92140,92143,92144],{},[996,92141,92142],{},"Python Developer:"," The most direct option you have after learning Python. Being a developer, you will write efficient code, create websites and use APIs, for example. Use your knowledge to:\n",[958,92145,92146,92152],{},[961,92147,92148,92151],{},[996,92149,92150],{},"Back-end (US$ 53k):"," Use Flask or Django to design services and systems. The basis for every website can be done with Python, implementing the logic behind the application and database interactions.",[961,92153,92154,92157],{},[996,92155,92156],{},"Front-end (US$ 49k):"," Not so common as back-end applications, you can still use Python to create interfaces in software applications using Python. Use Brython/Tkinter/PySide to use your back-end and show a UI.",[842,92159,92160],{},"Furthermore, Python can be used for general task automation, security purposes, and game programming. A lot of industries are watching Python as a desirable skill. One can see an increase in the job advertising asking Python for Product Managers, Financial jobs, Educators, and even Journalists!",[842,92162,92163],{},"In these projects, you will build web applications, databases, and data-related processes, machine learning, and data science projects.",[863,92165,92167],{"id":92166},"companies-that-are-using-python","Companies that are using Python",[842,92169,92170],{},"As a very flexible language, Python is applied in several fields. Giant tech companies such as Google, Facebook, Netflix, and IBM have been using it in their products. But this should not be a surprise by now. Here are a couple of successful startups based on Python:",[842,92172,92173,92180,92181,92188,92189,92196,92197,92204,92205,92212,92213,92220,92221,92228],{},[846,92174,92177],{"href":92175,"rel":92176},"https://plotly.com/",[850],[996,92178,92179],{},"Plotly"," is a library that allows ML Engineers and Data scientists to share interactive data visualization. The Canadian company has already raised $14M to develop its product. Developing software for self-driving vehicles, ",[846,92182,92185],{"href":92183,"rel":92184},"https://www.five.ai/",[850],[996,92186,92187],{},"Five AI"," reached $2.7M to apply AI using Python. Looking for the industry and headquartered in Berlin, we have ",[846,92190,92193],{"href":92191,"rel":92192},"https://www.micropsi-industries.com/",[850],[996,92194,92195],{},"Micropsi Industries"," enhancing industrial robots with Artificial Intelligence. ",[846,92198,92201],{"href":92199,"rel":92200},"https://lincolnloop.com/",[850],[996,92202,92203],{},"Lincoln Loop"," is a software development agency specialized in Python and Django development for web and mobile. For translation, we got Weglot offering solutions for several websites. As a marketing platform, ",[846,92206,92209],{"href":92207,"rel":92208},"https://ometria.com/",[850],[996,92210,92211],{},"Ometria"," leverages AI to help the retail industry to improve customer experience. To help travelers to find tickets and hotels, ",[846,92214,92217],{"href":92215,"rel":92216},"https://virail.com/",[850],[996,92218,92219],{},"Virail"," creates a product using Python technology. And for the healthcare industry, a good example is ",[846,92222,92225],{"href":92223,"rel":92224},"https://cureatr.com/",[850],[996,92226,92227],{},"Cureatr",", which is a medication management platform.",[842,92230,92231],{},"At MusicTech Lab, Python is one of our most-used programming languages. We build platforms for the music industry, creative tech, and beyond — and Python powers much of our backend and data work.",[1045,92233,92235,92238],{"className":92234},[13033,50238,50239,1052],[50241,92236],{"color":50243,"label":92237,"target":50245,"to":4946,"variant":50246},"Work with Us",[50241,92239],{"color":50249,"label":12840,"target":50245,"to":12838,"variant":50246},{"title":728,"searchDepth":729,"depth":729,"links":92241},[92242,92243,92244,92245],{"id":92042,"depth":729,"text":92043},{"id":92049,"depth":729,"text":92050},{"id":92108,"depth":729,"text":92109},{"id":92166,"depth":729,"text":92167},"2021-07-30T00:00:00.000Z","Why Python is one of the most popular programming languages. Its flexibility spans backend, AI, web development, and automation with an easy learning curve.",{"src":92249},"/images/blog/musictechlab_blog_why-the-programming-world-loves-python.webp",{"enabled":738,"items":92251},[92252,92254,92256],{"text":92253,"icon":3920},"Python was named TIOBE Language of the Year in 2020 for the fastest-growing popularity.",{"text":92255,"icon":5504},"Data Engineers ($65k) and Data Scientists ($58k) are the highest-paying Python roles.",{"text":92257,"icon":1067},"Companies from Google and Netflix to startups like Plotly and Five AI rely on Python in production.",{},{"title":690,"description":92247},[18784],"KvLYU2e9Rrpm4DSMVyqexVUebc53hTxq7aonlEFoQM0",{"id":92263,"title":570,"authors":92264,"badge":723,"body":92267,"category":756,"client":723,"date":92314,"description":92315,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":92316,"keyTakeaways":92318,"meta":92326,"navigation":738,"path":571,"seo":92327,"status":723,"stem":572,"tags":92328,"teaser":723,"__hash__":92329},"posts/blog/software-development/kanban-board-methodology-hack-your-companys-productivity.md",[92265],{"name":90672,"avatar":92266},{"src":90674},{"type":725,"value":92268,"toc":92309},[92269,92271,92274,92278,92280,92284,92287,92290,92297,92301,92304,92307],[842,92270,40867],{},[842,92272,92273],{},"Improving productivity is every company’s aim. Regardless of whether your company is large or small, you want your team working at its best. Good organisation, staff incentives to keep your workforce motivated - there are many approaches to improving productivity levels. But the Kanban board methodology is straightforward and highly effective. Below are 5 reasons why using the Kanban method can help your company’s productivity.",[863,92275,92277],{"id":92276},"what-is-the-kanban-board-method","What is the Kanban board method?",[842,92279,91958],{},[863,92281,92283],{"id":92282},"how-can-it-help-productivity","How can it help productivity?",[842,92285,92286],{},"Because it gives a visual picture of each project, it can improve productivity by enabling every team member to stay in control of their designated task, as they can clearly see which bit has been done and what needs doing next. Secondly, it can also help to protect existing productivity levels, by prioritising tasks to ensure that staff don’t slow down because they’re overwhelmed.",[842,92288,92289],{},"Every project encounters difficulties and changing requirements. The third reason why the Kanban method can help productivity is that it allows you to make changes in real time, thus avoiding errors and setbacks. The board owner can edit tasks into new sequences if priorities change, allowing each team member to stay fully up-to-date and maintain their productivity. This brings us to the fourth benefit: changing production to meet customer demand. Companies need to respond to their customers’ needs, which can change according to new trends and buying patterns. Because the Kanban method is so flexible, tasks can be reordered to suit customer demand without causing too much disruption or wasted work; the ability to reorganise and change priorities means that companies can respond to customers in real time.",[842,92291,92292,92296],{},[1027,92293],{"alt":92294,"src":92295},"Kanban board in Jira project management tool","/images/blog/musictechlab_blog_kanban-jira.gif","Source: Atlassian Jira",[863,92298,92300],{"id":92299},"will-it-help-productivity-at-my-company","Will it help productivity at my company?",[842,92302,92303],{},"Lastly, the Kanban method can help productivity through its diversity - it can be used across a wide range of industries (from keeping track of sales leads to recruitment), meaning that it can help your company to improve its productivity levels whatever your needs.",[842,92305,92306],{},"At Bravelab we love productivity and results, that’s why we have integrated the methodology of Kanban in our processes, work teams, and workflow. We use software made by Atlassian and called Jira. There we gather all projects and tasks to do, classify them by department, and assign them to every member of the team according to our needs. This helps us to manage and track the projects with high efficiency and most importantly, we deliver top results on time to every client.",[842,92308,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":92310},[92311,92312,92313],{"id":92276,"depth":729,"text":92277},{"id":92282,"depth":729,"text":92283},{"id":92299,"depth":729,"text":92300},"2021-07-23T00:00:00.000Z","Five reasons why the Kanban board methodology improves team productivity. Learn how visual task management helps prioritize work and adapt to changes.",{"src":92317},"/images/blog/musictechlab_blog_kanban-board-methodology-hack-your-companys-productivity.webp",{"enabled":738,"items":92319},[92320,92322,92324],{"text":92321,"icon":85476},"Kanban breaks work into To Do, Doing, and Done columns for visual progress tracking.",{"text":92323,"icon":2939},"Real-time task reordering lets teams respond to changing customer demands without disruption.",{"text":92325,"icon":4845},"Kanban works across industries, from sales lead tracking to recruitment pipelines.",{},{"title":570,"description":92315},[74615],"LDLF6foxTxJF8__MrTtW60HoT8DGrwgC6KuCrZfQ2kE",{"id":92331,"title":666,"authors":92332,"badge":723,"body":92335,"category":756,"client":723,"date":92422,"description":92423,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":92424,"keyTakeaways":92426,"meta":92434,"navigation":738,"path":667,"seo":92435,"status":723,"stem":668,"tags":92436,"teaser":723,"__hash__":92437},"posts/blog/software-development/warning-why-artificial-intelligence-will-defeat-traditional-hr.md",[92333],{"name":90672,"avatar":92334},{"src":90674},{"type":725,"value":92336,"toc":92417},[92337,92339,92342,92346,92349,92352,92355,92358,92361,92365,92368,92371,92374,92379,92382,92385,92392,92395,92401,92407,92415],[842,92338,40867],{},[842,92340,92341],{},"Artificial algorithms, chatbots, and systems cataloging data and selecting the best candidates for a given position are used to correctly verify employee candidates. AI now has the ability to process huge amounts of data, including candidate profiles from different backgrounds, countries, and skills. For medium and large companies, this type of solution is mandatory. On the other side, the pandemic has pushed companies to make restructuration in many cases, this has caused them to make them look for different types of profiles like candidates from overseas and rehire rushing after the COVID crisis has been controlled in some regions. This is leading us to a world where HR is operated by AI technologies. The big two questions are if computer data verification is able to completely replace an HR department inside a company? And what companies should do from now to face a new era? Let’s start discussing three main issues:",[863,92343,92345],{"id":92344},"intelligence-reach","Intelligence Reach",[842,92347,92348],{},"The method of recruiting new employees has changed significantly. What to do when several thousands of potential specialists apply for a job in a large corporation? Verification of applications, even by a several-person HR department, may then be significantly difficult. However, innovative systems and algorithms that impartially catalog candidates and verify their competencies in terms of requirements come to the rescue. Thanks to this, recruiters have constant access to databases, and it is easier for them to choose the right person for a required position. Artificial intelligence abilities have been developed up to frontiers never imagined a few years ago reaching an unprecedented transcendence. Its potential to do multiple complex tasks at the same time includes finding the right profile from a database that would include thousands of facts and information, candidates from different countries, backgrounds, and profiles.",[842,92350,92351],{},"We also need to keep in mind a key factor: this intelligence is becoming cheaper. The resources, talent, and efforts invested from different clusters around the world are doing the AI something cheaper and able to hire even from smaller companies. The capacity to find the right talent at a lower cost makes these types of platforms a new way to walk and a risk for the traditional personnel who work in HR.",[863,92353,92354],{"id":15110},"Availability",[842,92356,92357],{},"Chatbots as automatised tools in HR supply the immediate reaction time that today’s users expect once they have hiring queries, edge issues, or coaching problems. HR platforms with AI use chatbots which are able to simulate person-to-person conversations at the top level. In traditional HR departments, the strategy depends fully on the availability of the personnel, which makes it a disadvantage against companies where systems are working 24/7 searching for the best talent. In some specific fields, this becomes more critical, for example in IT, driverless technologies, financial specialists, or similar industries, the lack of talent makes companies run desperately in search of the few candidates available.",[842,92359,92360],{},"AI makes it possible to never stop companies running for top talent. We are inside a war of talent, big corporations are looking around the planet for this type of specialisation for their secret projects. Just keep the example of driverless war, according to some reports Apple is creating their next big project -a car-. Since 2015 it is known that “Titan Project”** which is the code name for this project has hired many engineers specialized in driverless and design for cars. From that time, there has been a war between Apple, Tesla, and other tech companies looking for top engineers and technicians to create a team dedicated to reaching their own goals. They not only look to the US as candidates to fulfill their needs, but they are also like a big dragon surfacing all the Earth searching for talent. And who will do it? Human beings from their offices searching in websites, social media groups, or making calls? Of course not! At that level, they require the benefits of AI to make thousands of searches every day with high accuracy. So? AI is becoming more and more crucial for HR vision.",[863,92362,92364],{"id":92363},"analytics-on-real-time","Analytics on Real-time",[842,92366,92367],{},"To make the best decision in any field, decision-makers need reliable data. HR platforms based in AI have the capability to bring data in real-time, but it is not only to give you tons of information, the most interesting side is these platforms allow decision-makers to have updated information in real-time, and the data that they really need with profiles adapted to their expectations. Most companies are running over the time to launch the latest trending products, provide demanding services, and execute projects faster than the competence. In those cases, platforms with AI bring accurate data and make it possible to find talent in less time. Compared with traditional HR, it is possible to achieve the goals under pressure, create powerful teamwork and achieve the goals for every project.",[842,92369,92370],{},"Keeping into consideration the three elements discussed above, we can set the idea that AI will replace traditional HR in a few years more, this is not a utopia. Corporations and even medium-small firms will require to create or hire their own platforms to manage lots of applications and recruitment processes. By 2030 we will see a new face in HR, there is no doubt. HR will be reduced in terms of employees, with automated processes in almost every corner, and using powerful AI platforms to find talent around the world, and going a bit further, in the next decade -2030’s- the traditional HR will be something about corporate history, because machines will take the place of humans and recruitment process will be something purely automatised.",[842,92372,92373],{},"So, companies of any size have homework to do now, to start implementing most of these new technologies inside HR teams in order to compete with the real trends on the market. To achieve that goal companies could start from now creating their own automated processes inside HR, hire platforms with AI, or in many cases like big companies, start developing their own systems and web platforms to locate talent in an accurate way. Those platforms should be connected to already existing databases which will help to create a powerful engine to run their objectives.",[842,92375,92376],{},[996,92377,92378],{},"At Bravelab we have years of experience creating complex and advanced platforms. In terms of the Human Resources sector, our company has the leadership, tools, and resources to create sophisticated and efficient web platforms that help companies to find talent in the most efficient way, boosting their abilities to create strong team works.",[842,92380,92381],{},"Would you like to know more about our solutions?",[842,92383,92384],{},"We invite you to have a free consultation e-meeting with one of our experts.",[842,92386,92387,92388],{},"Daniel Sąsiadek, ",[846,92389,92391],{"href":92390},"mailto:daniel.sasiadek@bravelab.io","daniel.sasiadek@bravelab.io",[842,92393,92394],{},"REFERENCES",[842,92396,2775,92397],{},[846,92398,92399],{"href":92399,"rel":92400},"https://www.reuters.com/article/us-amazon-com-jobs-automation-insight-idUSKCN1MK08G",[850],[842,92402,27539,92403],{},[846,92404,92405],{"href":92405,"rel":92406},"https://appleinsider.com/inside/apple-car",[850],[842,92408,92409,92410],{},"Cover image courtesy of Freepik - Created by upklyak ",[846,92411,92414],{"href":92412,"rel":92413},"https://www.freepik.com/",[850],"www.freepik.com",[842,92416,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":92418},[92419,92420,92421],{"id":92344,"depth":729,"text":92345},{"id":15110,"depth":729,"text":92354},{"id":92363,"depth":729,"text":92364},"2021-07-14T00:00:00.000Z","How AI-powered recruitment tools, chatbots, and analytics are transforming HR departments. Why companies of all sizes need to adapt to stay competitive.",{"src":92425},"/images/blog/musictechlab_blog_warning-why-artificial-intelligence-will-defeat-traditional-hr.webp",{"enabled":738,"items":92427},[92428,92430,92432],{"text":92429,"icon":11614},"AI recruitment platforms process thousands of candidate profiles impartially and 24/7.",{"text":92431,"icon":72284},"AI-powered chatbots simulate person-to-person HR conversations at a fraction of the cost.",{"text":92433,"icon":3844},"Real-time analytics let decision-makers find the right talent faster than traditional HR departments.",{},{"title":666,"description":92423},[14100,74615],"7JigswDferOHkMuHXcjQiomjhYm3j5j2lvZjW5xhcOY",{"id":92439,"title":334,"authors":92440,"badge":723,"body":92443,"category":756,"client":723,"date":92518,"description":92519,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":92520,"keyTakeaways":92522,"meta":92532,"navigation":738,"path":335,"seo":92533,"status":723,"stem":336,"tags":92534,"teaser":723,"__hash__":92535},"posts/blog/software-development/a-quick-introduction-to-profit-sharing-implementation.md",[92441],{"name":834,"to":720,"avatar":92442},{"src":722},{"type":725,"value":92444,"toc":92510},[92445,92447,92450,92454,92457,92462,92473,92477,92480,92484,92487,92491,92494,92498,92501,92505,92508],[842,92446,40867],{},[842,92448,92449],{},"There are many reasons, and profit sharing is surely NOT one of them, but I think it is still an idea worth considering. In this article, I’ll share with you my experience with a simple implementation of profit sharing.",[863,92451,92453],{"id":92452},"my-definition-of-profit-sharing","My definition of profit sharing",[842,92455,92456],{},"Profit sharing is a simple system that is based on dividing X percent of company's net profit among all employees in the company. The amount should be equal for everyone who has worked through the whole quarter. Seniority doesn’t matter. Everyone gets the same amount of money. While it is easy to say, it is definitely much harder to implement. As you probably know, every benefit system can be cheated, but here is the trick - profit sharing is not one of them!",[842,92458,92459],{},[996,92460,92461],{},"There are only three simple rules:",[958,92463,92464,92467,92470],{},[961,92465,92466],{},"An employee needs to work the whole quarter",[961,92468,92469],{},"If the employee works part-time, he/she will receive proportionately less money and the remaining sum will be divided once again",[961,92471,92472],{},"Profit sharing takes place only when all the invoices are paid",[863,92474,92476],{"id":92475},"what-about-advantages","What about advantages?",[842,92478,92479],{},"Profit sharing is neither a gift nor a bonus. If the company makes profit, an employee will get a proportionate amount of money. If the company doesn’t make profit, there won’t be anything to share. On top of that, there are no complicated legal matters, as profit sharing is only an additional item on an employee's bill.",[863,92481,92483],{"id":92482},"lets-find-out-some-disadvantages","Let's find out some disadvantages",[842,92485,92486],{},"To start with, profit sharing lowers the results of the company. Second, cash flow will be worse, which is a real problem for small businesses. Finally, you will probably hear that seniors should have gotten more… OK… but not all seniors want to do tasks that juniors do. That’s why I decided to share the profit equally. I believe that everyone in the company has an impact on the company's growth.",[863,92488,92490],{"id":92489},"how-to-calculate-profit-sharing","How to calculate profit sharing?",[842,92492,92493],{},"The screenshot below shows how you can easily achieve it.",[863,92495,92497],{"id":92496},"is-this-idea-worth-implementing-in-the-company","Is this idea worth implementing in the company?",[842,92499,92500],{},"Up to the point of writing, we had only three iterations of profit sharing. Two of them happened before the pandemic (in 2019). We approached the idea again this year. It's still hard to say how profit sharing increases the feeling of ownership. For sure it is worth trying and we will definitely continue sharing our profit in the next quarters.",[863,92502,92504],{"id":92503},"what-do-you-think-about-profit-sharing","What do you think about profit sharing?",[842,92506,92507],{},"As a Manager / Founder / CEO, would you be willing to implement something like that in your company? As an employee, do you see value?",[842,92509,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":92511},[92512,92513,92514,92515,92516,92517],{"id":92452,"depth":729,"text":92453},{"id":92475,"depth":729,"text":92476},{"id":92482,"depth":729,"text":92483},{"id":92489,"depth":729,"text":92490},{"id":92496,"depth":729,"text":92497},{"id":92503,"depth":729,"text":92504},"2021-06-23T00:00:00.000Z","A practical guide to implementing profit sharing in your company. Learn how dividing net profit among employees boosts ownership and motivation.",{"src":92521},"/images/blog/musictechlab_blog_a-quick-introduction-to-profit-sharing-implementation.webp",{"enabled":738,"items":92523},[92524,92526,92528,92530],{"text":92525,"icon":1774},"Profit sharing divides X% of net profit equally among all employees each quarter.",{"text":92527,"icon":4845},"Seniority does not matter; everyone receives the same amount.",{"text":92529,"icon":50270},"Only three rules: work the full quarter, pro-rate part-time, and all invoices must be paid.",{"text":92531,"icon":3920},"It increases ownership feeling but reduces short-term cash flow for small businesses.",{},{"title":334,"description":92519},[74615],"2bwUUByAtcLskbfrbmQSc77IGBGyOzxt0y5KTv2O3bE",{"id":92537,"title":642,"authors":92538,"badge":723,"body":92541,"category":756,"client":723,"date":92807,"description":92808,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":92809,"keyTakeaways":92811,"meta":92819,"navigation":738,"path":643,"seo":92820,"status":723,"stem":644,"tags":92821,"teaser":723,"__hash__":92822},"posts/blog/software-development/the-very-first-attempt-to-implement-4dx-in-bravelab-io.md",[92539],{"name":834,"to":720,"avatar":92540},{"src":722},{"type":725,"value":92542,"toc":92799},[92543,92545,92548,92552,92555,92569,92575,92578,92585,92595,92598,92609,92612,92615,92618,92622,92625,92628,92642,92645,92662,92665,92668,92673,92676,92688,92694,92701,92705,92719,92723,92726,92737,92741,92761,92765,92791,92794,92797],[842,92544,40867],{},[842,92546,92547],{},"I remember a dinner with Tomek Kaczanowski where he asked me: “Mariusz, did you visualize what you want to achieve in this project? What is your goal? Where are you? Does your team understand the goal in the same way as you?” He caught me off guard with these questions. I didn't know what to say so we started looking into these problems together. Why am I writing about it? Because almost two years after our discussion, we came back to 4DX - a method Tomek recommended. We have been using 4DX since March 2021. Let’s find out what this is for.",[863,92549,92551],{"id":92550},"a-quick-introduction-to-4dx","A quick introduction to 4DX",[842,92553,92554],{},"4DX is a strategy execution framework that defines four key principles:",[958,92556,92557,92560,92563,92566],{},[961,92558,92559],{},"Focus on a wildly important goal",[961,92561,92562],{},"Define LAG/LEAD Measures",[961,92564,92565],{},"Create Scoreboard(s)",[961,92567,92568],{},"Practice wildly important goals sessions",[842,92570,92571,92574],{},[996,92572,92573],{},"Focusing on wildly important goals"," is my favorite principle. It helps to define what is essential for you and your company. You can choose only up to two goals. It requires getting rid of activities that are not related to these goals. Thanks to this principle, you will be able to focus on essential matters.",[842,92576,92577],{},"**LAG Measure **- it can be your big goal. Something that you want to achieve with your Team, but it’s tough to measure itself. It can be “Increase revenue from 1 mln EUR to 2 mln EUR at the end of the year”, etc. Defining a LAG measure is not so complicated if you use the formula “from X to why by when.”",[842,92579,92580,92581,92584],{},"A much more complicated thing for me is to define the ",[996,92582,92583],{},"LEAD measure",". We can still use this formula above, but we need to figure out if this measure will help us improve the LAG scoreboard.",[842,92586,92587,92588,92591,92592,861],{},"Based on the LAG example above, we can define LEAD measures like: ",[996,92589,92590],{},"“Increase our monthly revenue from 250k EUR to 500k EUR”",". This LEAD measure can have another one, e.g., ",[996,92593,92594],{},"“Increase sales calls from two to four per day”",[842,92596,92597],{},"As I mentioned, creating LEAD measures is not so easy:",[958,92599,92600,92603,92606],{},[961,92601,92602],{},"We need to turn the problem into numbers.",[961,92604,92605],{},"We need to set a deadline.",[961,92607,92608],{},"It should be predictable and influenceable.",[842,92610,92611],{},"Ok, let’s assume that we have our LAG(s) and LEAD(s) measure(s). The next step is to create a scoreboard. In my experience, only your imagination is a limitation. Based on the dozen or so scoreboards that I have made, I can summarize that 90% of scoreboards fill out the date and value of measurement.",[842,92613,92614],{},"**WIG Session (Wildly important goal session) **is the meeting when the Team is updating numbers together. This meeting shouldn’t last more than 30 minutes.",[842,92616,92617],{},"To summarize, 4DX is not so hard to understand. Each attempt to implement 4DX brings you closer to achieve your objectives.",[863,92619,92621],{"id":92620},"real-life-example","Real-life example",[842,92623,92624],{},"In the middle of March 2021, I asked our business developers what is vital for the company in 1Q/2021. They could write max two goals. When we have finished, everyone has a different idea. Including me. For instance:",[842,92626,92627],{},"To hire five developers",[958,92629,92630,92633,92636,92639],{},[961,92631,92632],{},"To complete a company’s website",[961,92634,92635],{},"To hire Marketing Manager",[961,92637,92638],{},"To implement ATS tools",[961,92640,92641],{},"To deliver all of the opened projects",[842,92643,92644],{},"“Can we try to rephrase your goal based on the: from X to Y by when formula?” I asked. This is what happened:",[958,92646,92647,92650,92653,92656,92659],{},[961,92648,92649],{},"To hire five developers => To Increase the development team from 15 to 20 people until the end of the April",[961,92651,92652],{},"To finish a company’s website => To finish five pages that are still not ready by 31/03/2021",[961,92654,92655],{},"To hire Marketing Manager => To hire Marketing Manager by the end of the April",[961,92657,92658],{},"To implement ATS tools => To fill out 25 CV’s which are on hr@bravelab to the new ATS by the end of the March",[961,92660,92661],{},"To deliver all of the opened projects => To issue invoices for projects A, B, C by the end of the March",[842,92663,92664],{},"Looks better? Of course. Wait. Something is missing. Do these goals are predictable and influenceable? Do these goals align with our strategy for this year? Why do we want to hire five developers? Why do we want to implement ATS tools? What is our “wildly” important goal?",[842,92666,92667],{},"It quickly turned out that the aim “To deliver projects A, B, C by the end of March” could be crucial. On top of that, we noticed that we had a few unpaid client’s invoices. Then we wrote down a new goal:",[842,92669,92670],{},[996,92671,92672],{},"To decrease unpaid invoices from 80k to 0k by the end of March 2021",[842,92674,92675],{},"Finally! We’ve got it. Two wildly important goals:",[958,92677,92678,92683],{},[961,92679,92680],{},[996,92681,92682],{},"To issue invoices for all contracted projects by the end of March 2021",[961,92684,92685],{},[996,92686,92687],{},"To decrease unpaid invoices from 84k to 0 by the end of March 2021",[842,92689,92690,92691,861],{},"We still didn’t have LEAD measures. So we decided to use projects' spreadsheets where we have income/expenses financial data. At this point, we were able to focus on these projects where we had some issues with delivery. ",[996,92692,92693],{},"Everything else was put on hold",[842,92695,92696,92697,92700],{},"The next step was to create ",[996,92698,92699],{},"the scoreboards",". We didn’t find an appropriate online app, we decided to use google spreadsheets. See the image below how we measured unpaid invoices:",[863,92702,92704],{"id":92703},"what-has-worked","What has worked",[958,92706,92707,92710,92713,92716],{},[961,92708,92709],{},"We defined two wildly important goals (it required 3 hours of meetings)",[961,92711,92712],{},"We established WIG sessions every Monday at 1 p.m",[961,92714,92715],{},"Our scoreboard was updated with accurate data",[961,92717,92718],{},"We were focused mainly on these two goals. It was easy to hold on to unnecessary ideas",[863,92720,92722],{"id":92721},"what-has-not-worked","What has not worked",[842,92724,92725],{},"WIG Sessions required more than 30 minutes. Sometimes up to 1h",[958,92727,92728,92731,92734],{},[961,92729,92730],{},"People who will be using 4DX should learn the basics of this method before we start",[961,92732,92733],{},"Everyone has to be prepared before the WIG Session (everyone needs to know their current measurements. It's important to put them out on the scoreboard together)",[961,92735,92736],{},"LEAD measures didn’t work. Not everyone was able to define what he needs to do to bring our efforts closer to our LAG measure.",[863,92738,92740],{"id":92739},"what-can-help-me-to-understand-4dx","What can help me to understand 4DX",[958,92742,92743,92746,92754],{},[961,92744,92745],{},"First: The Four Disciplines of Execution book",[961,92747,92748,92749],{},"If you have heard about OKRs, you will see some common things. If you want to know more, there is a good comparison article ",[846,92750,92753],{"href":92751,"rel":92752},"https://zizou.stronazen.pl/manual_BNP_FINAL.pdf",[850],"https://www.perdoo.com/resources/okr-vs-4dx/.",[961,92755,92756,92757],{},"One of my favorite books about focusing on what matters: ",[846,92758,92759],{"href":92759,"rel":92760},"https://www.amazon.com/Essentialism-Disciplined-Pursuit-Greg-McKeown/dp/0804137382",[850],[863,92762,92764],{"id":92763},"bonus","Bonus",[958,92766,92767,92774,92777,92784],{},[961,92768,92769,92770,861],{},"If you want to see how it works, I prepared an example scoreboard for you. Feel free to use this spreadsheet: ",[846,92771,92772],{"href":92772,"rel":92773},"https://docs.google.com/spreadsheets/d/1Btvv4JVujR3UaEI4saled00JjsqCUCdp_n--mRHSMNQ/edit#gid=631824341",[850],[961,92775,92776],{},"We’re working on some Proof of Concept online app. If you want to join our beta testers group (for free forever), please contact me via LinkedIn.",[961,92778,92779,92780],{},"Read more about 4DX in practice ",[846,92781,92782],{"href":92782,"rel":92783},"https://tomek.kaczanowscy.pl/2019/06/4dx-implementation/",[850],[961,92785,92786,92787,861],{},"If you want to start with 4DX, this article is a good point to start ",[846,92788,92789],{"href":92789,"rel":92790},"https://www.franklincovey.com/solutions/execution/4-disciplines/",[850],[842,92792,92793],{},"PS: Remember. 4DX is about execution. Almost everything in this method boils down to work on habits in your company. I wish you the best of luck in implementing 4DX! :)",[842,92795,92796],{},"Pssst... Less is more",[842,92798,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":92800},[92801,92802,92803,92804,92805,92806],{"id":92550,"depth":729,"text":92551},{"id":92620,"depth":729,"text":92621},{"id":92703,"depth":729,"text":92704},{"id":92721,"depth":729,"text":92722},{"id":92739,"depth":729,"text":92740},{"id":92763,"depth":729,"text":92764},"2021-06-22T00:00:00.000Z","4DX is a strategy that helps achieve your goal in a project, see where you are, and help also the team to understand the goal in the same way as you.",{"src":92810},"/images/blog/musictechlab_blog_the-very-first-attempt-to-implement-4dx-in-musictechlab-io.webp",{"enabled":738,"items":92812},[92813,92815,92817],{"text":92814,"icon":50270},"4DX limits focus to max two wildly important goals using the formula 'from X to Y by when.'",{"text":92816,"icon":3844},"LEAD measures must be predictable, influenceable, and expressed in concrete numbers.",{"text":92818,"icon":3271},"Weekly WIG sessions keep the scoreboard updated but often exceed the recommended 30 minutes.",{},{"title":642,"description":92808},[74615],"89ImLX_mrBvzDf5m28dP7No5JZlm9yTRqBNQ3RdMnAs",{"id":92824,"title":630,"authors":92825,"badge":723,"body":92828,"category":756,"client":723,"date":93010,"description":93011,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":93012,"keyTakeaways":93014,"meta":93022,"navigation":738,"path":631,"seo":93023,"status":723,"stem":632,"tags":93024,"teaser":723,"__hash__":93025},"posts/blog/software-development/the-case-of-colonial-pipeline-and-7-security-reminders.md",[92826],{"name":90672,"avatar":92827},{"src":90674},{"type":725,"value":92829,"toc":93008},[92830,92832,92835,92838,92841,92844,92847,92850,92853,92858,92869,92874,92885,92890,92898,92903,92932,92937,92948,92953,92964,92969,92977,92980,92983,92986,92988,92993,92998,93006],[842,92831,40867],{},[842,92833,92834],{},"The latest events in the Colonial pipelines show the intense war behind the networks between rival nations and their eagerness to destabilize strategic nations. Does it affect too small-medium businesses? How to prevent it?",[842,92836,92837],{},"You wake up, as usual, to get ready for your job. When you want to take a shower, you immediately notice that energy has been shut down in the whole house. After that, you take your mobile phone, turn on the device’s light and look where to walk. But there is a surprise, your mobile phone looked discharged and turned off. You grope to hold on to the bed and look for the nearest window. When the first light in the sky looks over the streets, you notice there are people in pajama out on the streets walking and trying to understand what's going on. You go out, and after asking others, they tell you that basic services are shut down in all the town, including the internet and all types of communications. What’s happening? - you asked desperately.",[842,92839,92840],{},"No, it is not an alien invasion, it is not a zombie attack, and it is not a nuclear war either. It could be something worse, a cyber-war, looks invisible but like gravity, you can feel it everywhere. A few years ago, Russia's president, Vladimir Putin, assured that the next global potential would be defined in who would have the ability to control the networks and have enough skills, knowledge, and resources to win a cyberwar. The world now is hyper-connected. Every single human depends on the network, nobody escapes. During the last years, there has been an escalation between nations to reach the control of networks over enemies and even allies. Every day, nations and corporations are taking seriously this thread, probably more than others like climate change or terrorism. But the question is if this kind of thread could be prevented and talking from the business perspective, the emerging question is if it is something able to avoid or prevent for small-medium businesses.",[842,92842,92843],{},"In some ways, it looks like if the biggest corporations and public organisations are affected, there is no way to escape from a threat like this if you manage a small or medium business. On the other hand, for many startups or businesses, investing time and resources in these types of issues is not worth it. There are two types of thinking, one that believes that a small or medium business would never be a target for hidden organisations sponsored by governments, or another thought is that when the “tiger attacks, the tiger will bit for sure”, so, if there is part of a cyber attack, there will be no way to escape.",[842,92845,92846],{},"Both beliefs could be true in some way, but from a realistic perspective, there is another side to the coin. From one side, small and medium businesses are good targets for individual hackers or smaller criminal groups, but no less dangerous. In the past days, a guy from Romania was arrested in Mexico accused of being ahead of a criminal organisation in the southeast of Mexico to operate credit card cloning and theft of bank data from small businesses and individuals in tourist cities like Cancun. It means that nowadays any type of business are targeted by criminal groups, banks, clients and owner’s data could be stolen easily. And if the business manages a platform to receive payments or operate the business, it is more possible that this platform would become a favourite dish for this type of criminal. So, don’t believe you would never be targeted, every day your business is at risk, this is a war from different levels and dimensions, but unfortunately, we need to accept that we are vulnerable.",[842,92848,92849],{},"The second belief is that when the tiger attacks, there is no room to escape. It could be possible, some attacks are targeted at big companies like Google or Amazon where a lot of data is stored and trusted by other smaller companies. We could be involved in this type of situation; however, it is possible to prevent leaks of data, hacks to business platforms, and theft of bank information or money. What can we do then?",[842,92851,92852],{},"For years, our Bravelab team has been specialising in the creation and development of different kinds of web platforms, from standard ones up to complex ones that require a senior-level team, and lots of hours spent in coordination with different teams and key members. As part of years of experience, we share some essential and useful advice on how to prevent the damage of potential cyber-attacks. We called this a “corporate culture to make our daily life cyber safely”:",[842,92854,92855],{},[996,92856,92857],{},"(01) Keep software updated.",[958,92859,92860,92863,92866],{},[961,92861,92862],{},"Turn on Automatic Updates for your operating system.",[961,92864,92865],{},"Use web browsers such as Safari, Chrome, or Firefox that receive frequent, automatic security updates.",[961,92867,92868],{},"Make sure to keep browser plug-ins (Flash, Java, etc.) up-to-date.",[842,92870,92871],{},[996,92872,92873],{},"(02) Good password management.",[958,92875,92876,92879,92882],{},[961,92877,92878],{},"Use password manager applications to keep your passwords safer in one site and allow them to remember different ones for each site.",[961,92880,92881],{},"For obvious reasons, encourage your work team not to use the same password for all apps and sites, you can use the browser password generator to keep different ones.",[961,92883,92884],{},"Update passwords periodically, especially for those logins more used in your company, like email access, banking, contracts folders, etc.",[842,92886,92887],{},[996,92888,92889],{},"(03) Train your team to identify suspicious emails.",[958,92891,92892,92895],{},[961,92893,92894],{},"Give some workshops or sessions to train your employees on how to identify suspicious emails. Usually, some tricks help to see clear differences between real brand emails and fake ones.",[961,92896,92897],{},"If your company has more than 50 employees and your main service is nothing about IT, you can hire security consultants or advisors to give brief conferences to your teams.",[842,92899,92900],{},[996,92901,92902],{},"(04) Use mobile devices safely and wisely.",[958,92904,92905,92908,92911,92914,92917],{},[961,92906,92907],{},"Don't click on links or attachments from unsolicited or unknown emails.",[961,92909,92910],{},"Only install apps from trusted sources like Apple AppStore or Google Play.",[961,92912,92913],{},"Avoid downloading files from free resources unless you are 100% of the website. Remember the rule, “nothing is free in this world”.",[961,92915,92916],{},"Keep the device's operating system up to date.",[961,92918,92919,92920,92925,92926,92931],{},"Use Apple's ",[846,92921,92924],{"href":92922,"rel":92923},"https://www.apple.com/icloud/find-my-iphone.html",[850],"Find my iPhone"," or the ",[846,92927,92930],{"href":92928,"rel":92929},"https://support.google.com/accounts/answer/6160491?hl=en",[850],"Android Device Manager"," tools to help prevent loss or theft.",[842,92933,92934],{},[996,92935,92936],{},"(05) Be careful which sites you click on.",[958,92938,92939,92942,92945],{},[961,92940,92941],{},"In the same situation with emails, many websites are fake and use known brands to steal data or money. It is also common that many different types of viruses could be leaked when you click those sites.",[961,92943,92944],{},"Train your employees to identify those websites, and teach them the basic security standards.",[961,92946,92947],{},"Some companies integrate blocks and apps to block suspicious websites.",[842,92949,92950],{},[996,92951,92952],{},"(06) Back up your data with cloud solutions.",[958,92954,92955,92958,92961],{},[961,92956,92957],{},"Invest in cloud solutions. These services can fit your needs depending on the size of your company and a load of information you require to save. Request at least 3 offers and evaluate pros and cons.",[961,92959,92960],{},"Research from which countries are coming to those cloud companies and where are they based. Try to hire services from companies based in countries with the highest transparency policies and safest law frameworks.",[961,92962,92963],{},"Ensure to make backups every day, in some cases where you have sensitive documents or relevant projects, ensure backups every hour or less.",[842,92965,92966],{},[996,92967,92968],{},"(07) Invest in anti-virus/anti-malware protection.",[958,92970,92971,92974],{},[961,92972,92973],{},"No matter the type of company you manage, number of employees, or even the operative system. Nowadays, it is necessary to invest in anti-virus systems.",[961,92975,92976],{},"Compare prices and benefits of 2 or 3 companies. Request a demo and explore their advantages or cons.",[842,92978,92979],{},"This new decade that has just begun is moving us to create more complex and efficient platforms for new market and business needs. The world is already hyper-connected and new technologies and systems will make us use more advanced platforms where cybersecurity will be a transcendental issue to invest more resources. Making this investment properly could define the future of a successful business.",[842,92981,92982],{},"In Bravelab, our solutions help startups and corporations to develop and maintain sophisticated and helpful platforms that promote a safe environment for our clients safeguarding all investments in good hands",[842,92984,92985],{},"Are you interested in developing a safe platform for your business or corporation? Let’s arrange a call with our executives. We would be glad to find the best solution for you.",[4937,92987],{},[842,92989,92990],{},[996,92991,92992],{},"CONTACT US",[842,92994,92995],{},[996,92996,92997],{},"Roberto Cruz / Marketing Manager",[842,92999,93000],{},[996,93001,93002],{},[846,93003,93005],{"href":93004},"mailto:roberto.cruz@bravelab.io","roberto.cruz@bravelab.io",[842,93007,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":93009},[],"2021-06-04T00:00:00.000Z","The Colonial Pipeline attack shows why every company must invest in cybersecurity. Seven practical reminders to protect your business from cyber threats.",{"src":93013},"/images/blog/musictechlab_blog_the-case-of-colonial-pipeline-and-7-security-reminders.webp",{"enabled":738,"items":93015},[93016,93018,93020],{"text":93017,"icon":3847},"Small and medium businesses are frequent targets for individual hackers and criminal groups.",{"text":93019,"icon":7495},"Seven practical security habits include auto-updates, password managers, and daily cloud backups.",{"text":93021,"icon":4845},"Training employees to identify phishing emails is one of the cheapest and most effective defenses.",{},{"title":630,"description":93011},[15279],"GxUl6Rji05JA58m1hZtF28SPh8UPwTb_rKSs1uHiSgA",{"id":93027,"title":478,"authors":93028,"badge":723,"body":93031,"category":756,"client":723,"date":93091,"description":93092,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":93093,"keyTakeaways":93095,"meta":93105,"navigation":738,"path":479,"seo":93106,"status":723,"stem":480,"tags":93107,"teaser":723,"__hash__":93108},"posts/blog/software-development/holacracy-why-and-where-we-are.md",[93029],{"name":834,"to":720,"avatar":93030},{"src":722},{"type":725,"value":93032,"toc":93085},[93033,93035,93038,93040,93044,93046,93049,93052,93056,93059,93063,93076,93080,93083],[842,93034,40867],{},[842,93036,93037],{},"Are you Agile? How fast are you able to respond to the changes in VUCA World? Are you an entrepreneur? Have you tried to build a company from scratch? Are you an employee? Were you being strangled by a lack of with lack of purpose?",[842,93039,52316],{},[863,93041,93043],{"id":93042},"is-a-hierarchical-structure-the-only-choice-for-your-company","Is a hierarchical structure the only choice for your company?",[842,93045,52316],{},[842,93047,93048],{},"Many systems allow us to organise our companies. However, most companies choose a default one: classic hierarchy. Let’s assume that you are CEO. First, you want to hire a mid-management level - the expert who will be managing other people. If you aim for this goal, you won’t have any daily issues to do. Only strategic ones. Really?",[842,93050,93051],{},"Now, let’s assume that you are an employee. You want to know what is your primary responsibility, what boundaries are. What is your role? Does your role involve what you want to do? What if some part of the company underperforms? Are you ready enough to switch yourself to another role? Are you willing to learn something new?",[863,93053,93055],{"id":93054},"does-the-flat-structure-work-for-the-company","Does the flat structure work for the company?",[842,93057,93058],{},"Bravelab is more than six years on the market. We’ve been growing organically. Between 2015-2021 we increased our revenue by ~51 times. We already have ~30 people on board. We started from - let’s say - a semi-flat structure. Everyone could talk with the CEO; everyone felt equal. That was the theory. Practically every single decision should have been made by the CEO. It slows down a company. We tried to have mid-level management. Still, the CEO was the central decision-making point - and the leading blocker of the company’s growth.",[863,93060,93062],{"id":93061},"attempt-to-develop-our-culture-based-on-non-default-options","Attempt to develop our culture based on non-default options",[842,93064,93065,93066,93071,93072,861],{},"Things have changed since I started to learn more about different business models (mainly in Business and IT Management ",[846,93067,93070],{"href":93068,"rel":93069},"https://www.agh.edu.pl/",[850],"AGH studies","). So, we started to be a full-transparent company. You can see rates, commissions, profits, and more as an employee. It was the most crucial step: attempt to develop our culture based on non-default options. We took what was the best from different company structures and systems and applied them at ",[846,93073,88176],{"href":93074,"rel":93075},"https://www.bravelab.io/",[850],[863,93077,93079],{"id":93078},"testing-holacracy-as-a-new-form-off-structure-for-companies","Testing Holacracy as a new form off structure for companies",[842,93081,93082],{},"As a manager/founder/CEO, have you faced similar issues at your company? As an employee, do you see value in it?",[842,93084,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":93086},[93087,93088,93089,93090],{"id":93042,"depth":729,"text":93043},{"id":93054,"depth":729,"text":93055},{"id":93061,"depth":729,"text":93062},{"id":93078,"depth":729,"text":93079},"2021-04-21T00:00:00.000Z","We have been testing Holacracy at Bravelab.io for 6 months. Companies are complex organisms. We have the space to experiment for ourselves.",{"src":93094},"/images/blog/musictechlab_blog_holacracy-why-and-where-we-are.webp",{"enabled":738,"items":93096},[93097,93099,93101,93103],{"text":93098,"icon":3920},"Revenue grew 51x between 2015 and 2021 with an organic growth strategy.",{"text":93100,"icon":3847},"CEO as sole decision-maker became the top blocker to company growth.",{"text":93102,"icon":50270},"Full financial transparency for all employees was the most crucial cultural step.",{"text":93104,"icon":4845},"Holacracy manages roles, not people, enabling self-organized teams.",{},{"title":478,"description":93092},[74615],"rfujZ0VYpsf9LC2ykd5GHSqO2Vus7vUEW5AonAaBK_g",{"id":93110,"title":402,"authors":93111,"badge":723,"body":93116,"category":756,"client":723,"date":93165,"description":93166,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":93167,"keyTakeaways":93169,"meta":93179,"navigation":738,"path":403,"seo":93180,"status":723,"stem":404,"tags":93181,"teaser":723,"__hash__":93182},"posts/blog/software-development/comparison-of-the-communication-channels-in-remote-work.md",[93112],{"name":93113,"avatar":93114},"Oresta Ventsak",{"src":93115},"/images/people/oresta-ventsak.webp",{"type":725,"value":93117,"toc":93162},[93118,93120,93123,93126,93130,93133,93141,93144,93151,93154,93157,93160],[842,93119,40867],{},[842,93121,93122],{},"Recently the vast changes have happened in the business world. The pandemic situation in 2020 has forced companies all over the world to build the process of remote work. The biggest risks companies face when working remotely are influence on employees’ effectiveness, engagement, and attitude, as well as the process of communication and knowledge sharing. Although it was very challenging in the beginning, now most of the companies looked at remote work from a different perspective. Not only people manage to work on a remote basis – they know how to do it effectively and beneficial.",[842,93124,93125],{},"One of the most important decisions to make is to decide on communication channels. The article presents a brief overview of the main business communication platforms.",[863,93127,93129],{"id":93128},"types-of-communication-channels","​Types of communication channels",[842,93131,93132],{},"Slack is a pioneer of business communication tools launched by an American Software company in 2013. Slack was designed to facilitate easier and faster business-to-business communication and replace emails. Keeping everything in one place is easily accessible by anyone working in the same team, reduces the number of repetitive questions, helps to keep everyone in a loop, and facilitates smooth onboarding of new people. The team is an alternative to Slack, which was released in 2017 by Microsoft as part of the Microsoft 365 group. Similar to Slack, its main aim is to bring all people working together at the same place and by that improve collaboration, reduce unnecessary emails, and keep communication transparent. Another Slack competitor is Chanty. Although Chanty is less popular, it positions itself as being the most robust alternative to Slack. The main advantage of Chanty in comparison to Slack is that it is faster and more affordable (75% cheaper). Moreover, Chanty has a built-in task manager.",[842,93134,93135,93136],{},"The table below presents an overview of the main features of the platforms. You can find the detailed comparison of the channels in the following file:",[846,93137,93140],{"href":93138,"rel":93139},"https://media.bravelab.io/filer_public/7e/14/7e1430d6-fb11-4edc-a350-fcc28f56e185/comparison_of_communiaction_channels.pdf",[850]," Comparison of communication channels.",[842,93142,93143],{},"​",[842,93145,93146,93150],{},[1027,93147],{"alt":93148,"src":93149},"Comparison table of Slack, Microsoft Teams, and Chanty communication platforms","/images/blog/musictechlab_blog_communication-channels.webp","## Choosing a channel to establish effective communication with a remote team",[842,93152,93153],{},"All three platforms have very similar main functions and are aimed at making the communication process faster, easier, and more transparent. However, there are still differences that should be considered depending on a company’s needs. It is crucial to decide on the features that are the most important for your business to choose the most suitable communication channel. While Slack and Teams are known almost to all businesses ranging from small start-ups to big corporations, Chanty is the least popular of the three platforms and therefore lacks enough reviews to base opinion on. It has fewer features and functions available than Slack and Teams. However, for small companies, its price and built-in task manager (not available in the other two business messengers) can be the best advantage.",[842,93155,93156],{},"Moreover, Chanty positions itself as being the easiest to use, and its main aim is to create an intuitive experience for its users. When comparing Slack and Teams, Slack is relatively easier to get started working with. While in Teams, you need first to configure your Office 365 domain.",[842,93158,93159],{},"Furthermore, Slack gives companies the possibility to invite their clients or partners as guests to the chosen channels, which has also improved external business communication and collaboration. On the other hand, Teams can be more affordable when a company has a Microsoft Business packet (Office 365), so a company does not need to pay for it separately. The biggest disadvantage of Chanty is its limited integrations. Last but not least - the design and layout of the platforms should be considered. Many customers distinguish Slack as being with the best user interface, but this factor is quite individual, so before making a decision it’s worth checking and comparing demo versions of the platforms.",[842,93161,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":93163},[93164],{"id":93128,"depth":729,"text":93129},"2020-10-26T00:00:00.000Z","A comparison of communication channels for remote teams. Explore the best platforms to keep your distributed workforce connected and productive.",{"src":93168},"/images/blog/musictechlab_blog_comparison-of-the-communication-channels-in-remote-work.webp",{"enabled":738,"items":93170},[93171,93173,93175,93177],{"text":93172,"icon":72284},"Slack, Microsoft Teams, and Chanty all aim to reduce email and improve transparency.",{"text":93174,"icon":5504},"Chanty is 75% cheaper than Slack and includes a built-in task manager.",{"text":93176,"icon":4845},"Slack allows inviting external clients as guests to specific channels.",{"text":93178,"icon":1774},"Teams can be free if your company already pays for Microsoft 365.",{},{"title":402,"description":93166},[74615],"fwKOiUGEZnk0cYqEjNhSBmGEMFqICs36VUyvtsse1xc",{"id":93184,"title":342,"authors":93185,"badge":723,"body":93190,"category":756,"client":723,"date":93311,"description":93312,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":93313,"keyTakeaways":93315,"meta":93325,"navigation":738,"path":343,"seo":93326,"status":723,"stem":344,"tags":93327,"teaser":723,"__hash__":93328},"posts/blog/software-development/automate-repetitive-tasks-to-improve-your-business-performance.md",[93186],{"name":93187,"avatar":93188},"Jurek Skowron",{"src":93189},"/images/people/jurek-skowron.webp",{"type":725,"value":93191,"toc":93304},[93192,93195,93199,93202,93213,93216,93219,93223,93226,93241,93244,93247,93251,93265,93269,93272,93275,93278,93292,93296,93299],[842,93193,93194],{},"As it was presented in the previous article, people and organizations benefit from repetition because of its importance in learning. But there is also a negative kind of repetition, hurting individuals and organizations. You can deal with dull repetitive tasks by reorganizing them. Nowadays, digital work and processes are ubiquitous for many of us, therefore, automation of digital tasks is also a powerful tool. There are services, like Zapier, which provide you a way to create automation workflows and integrate various computer tools, with no programming required.",[863,93196,93198],{"id":93197},"dealing-with-repetitive-tasks","Dealing with repetitive tasks",[842,93200,93201],{},"There are some ways to deal with tasks, which turn us, or our employees into mindless zombies. Some of the solutions are:",[958,93203,93204,93207,93210],{},[961,93205,93206],{},"altering routines,",[961,93208,93209],{},"delegating tasks to contractors and virtual assistants,",[961,93211,93212],{},"recycling projects using templates, organizing flows for such tasks, with setting daily reminders, milestones, and deadlines.",[842,93214,93215],{},"Some essential tasks can’t be delegated, then the only way is to figure out how to make them more enjoyable.",[842,93217,93218],{},"But in today’s digital world, we are also equipped with a powerful tool, or better to say, the wide range of tools provided by automation.\nArtificial intelligence is the present, and businesses are adopting it. But it is not just AI. There are many more ways to benefit from automation.\nAutomation is an example of how technology has fundamentally changed the way we live and work. We use it every day, e.g., when using auto-paying bills, creating calendar notifications, even setting a washing machine or coffee pot.\nFor work, nowadays, there are many code boilerplates, scripts, applications, macros, etc., which are convenient solutions for many monotonous jobs.",[863,93220,93222],{"id":93221},"automation-of-digital-tasks-on-your-own","Automation of digital tasks on your own",[842,93224,93225],{},"Not always, there is a ready solution for the automation of digital tasks. Then, in many cases, we can construct our own. If you think “code!” programming your solution - yes. But wait! Only sometimes.",[842,93227,93228,93229,93234,93235,93240],{},"I am sure you are acquainted with ",[846,93230,93233],{"href":93231,"rel":93232},"https://en.wikipedia.org/wiki/Jeff_Atwood",[850],"Jeff Atwood’s"," quote ",[846,93236,93239],{"href":93237,"rel":93238},"https://news.ycombinator.com/item?id=10979240",[850],"“the best code is no code at all”",". Reinventing the wheel is a good idea when you are learning how the wheel works or trying to disrupt the wheel technology. But for productivity, whenever possible, it is better to use a wheel already made, well tested in various scenarios. And to focus on this part of our job that adds value, changes/ saves the world, earns money (whatever your reasons and motivation). Thus, getting back to automating tasks before coding your solution, you should consider an easier and faster way.",[842,93242,93243],{},"It brings us to automation tools that connect different applications and services without coding or relying on developers to build the integration. Using these tools resembles building with Lego bricks. They provide us functional blocks that you can join and combine into your integration workflows. Among these services are Zapier, Automate.io, Integromat, IFTTT, and many more.",[842,93245,93246],{},"In the next article, I will show a use case of an automation task I solved for MusicTech Lab. After research, I decided to use Zapier to integrate Slack with Google Drive/Docs/Sheets. Let me briefly introduce the tool.",[863,93248,93250],{"id":93249},"what-is-zapier","What is Zapier?",[842,93252,93253,93254,93259,93260,93264],{},"In my research on how to automate the task I was given (I will elaborate on it in the next article), I found that the most common solution is to use ",[846,93255,93258],{"href":93256,"rel":93257},"https://zapier.com/",[850],"Zapier",". As you can read on ",[846,93261,93263],{"href":93256,"rel":93262},[850],"its website",",* \"Zapier is an online automation tool that connects your apps and services. You can connect two or more apps to automate repetitive tasks without coding or relying on developers to build the integration.\"*\nI have also found Zapier as easy to use and experiment with. What is more, it offers a free plan and gives us a 14-day trial of the premium plan, which enables us to check whether it is the right tool for our needs.",[863,93266,93268],{"id":93267},"creating-a-zap","Creating a Zap",[842,93270,93271],{},"In Zapier, you create Zaps, which are workflows containing two or more steps. You can create your Zaps or use some already predefined in Zapier.",[842,93273,93274],{},"Here you can see the overview of Zap I created:",[842,93276,93277],{},"As you can see on the screenshot, steps are visually represented as boxes, representing functional blocks, which you join to create a workflow. Every phase is configurable, with options specific to its type and application it handles. You can use actions related to specific applications (e.g., Google Docs, Slack, Todoist) or special helpers (for actions like conditions, delays, changing how data is formatted, or even adding your Python or JavaScript code).",[1045,93279,93281,93284,93288],{"className":93280},[1048,1049,1050,1051,1052],[1054,93282],{"description":93283,"title":93258},"The most popular no-code automation tool. Connects 5,000+ apps.",[1054,93285],{"description":93286,"title":93287},"Simple \"if this, then that\" automation. Great for personal workflows.","IFTTT",[1054,93289],{"description":93290,"title":93291},"Advanced visual workflows with branching logic and error handling.","Make (Integromat)",[863,93293,93295],{"id":93294},"a-use-case-at-musictech-lab","A Use Case at MusicTech Lab",[842,93297,93298],{},"Zapier is not the only automation service for digital tasks, but it was a great fit for the Slack + Google Drive/Docs/Sheets integration we built at MusicTech Lab.",[1032,93300,93301],{},[842,93302,93303],{},"Automate to have more time for people and things that matter. The best automation is the one you set up once and forget about.",{"title":728,"searchDepth":729,"depth":729,"links":93305},[93306,93307,93308,93309,93310],{"id":93197,"depth":729,"text":93198},{"id":93221,"depth":729,"text":93222},{"id":93249,"depth":729,"text":93250},{"id":93267,"depth":729,"text":93268},{"id":93294,"depth":729,"text":93295},"2020-09-18T00:00:00.000Z","Repetitive work may negatively impact your business performance. By improving business performance management, you can automate tasks and increase productivity.",{"src":93314},"/images/blog/musictechlab_blog_automate-repetitive-tasks-to-improve-your-business-performance.webp",{"enabled":738,"items":93316},[93317,93319,93321,93323],{"text":93318,"icon":87068},"No-code tools like Zapier connect 5,000+ apps without writing any code.",{"text":93320,"icon":2939},"Automation frees time for high-value work instead of repetitive digital tasks.",{"text":93322,"icon":5504},"Zapier offers a free plan and 14-day premium trial to test your workflows.",{"text":93324,"icon":50270},"Before coding a custom solution, check if an existing integration already exists.",{},{"title":342,"description":93312},[74615,15279],"H2Jc2Rn6O331reRfA8Os8TbRIxs6HZFRoPVxaPuFPbc",{"id":93330,"title":382,"authors":93331,"badge":723,"body":93336,"category":756,"client":723,"date":93436,"description":93437,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":93438,"keyTakeaways":93440,"meta":93448,"navigation":738,"path":383,"seo":93449,"status":723,"stem":384,"tags":93450,"teaser":723,"__hash__":93451},"posts/blog/software-development/bravelabs-team-about-productivity.md",[93332],{"name":93333,"avatar":93334},"Monika Niemiec",{"src":93335},"/images/people/monika-niemiec.png",{"type":725,"value":93337,"toc":93429},[93338,93340,93343,93347,93350,93354,93372,93376,93396,93400,93420,93424,93427],[842,93339,40867],{},[842,93341,93342],{},"Our company aware of self-improvement team that faces huge challenges every day, so while working on this article, It’s a good time to conduct short interviews with our employees asking them, among other things, what productivity is for them, what motivates them, and what tools they use to plan their tasks.",[863,93344,93346],{"id":93345},"productivity-definition","Productivity - definition",[842,93348,93349],{},"There are many definitions of productivity. According to the encyclopedia, it is giving good results in the production of something and the size of the production effect obtained from a given input. You can find many ways to improve your productivity by striving for maximum achievement. Many definitions of productivity show us that it might vary for every person. Therefore, we asked our developers how they perceive productivity.",[863,93351,93353],{"id":93352},"what-is-productivity-to-you","What is productivity to you?",[842,93355,93356,93357,93360,93361,93364,93365,93368,93369],{},"**Bartosz **- ",[964,93358,93359],{},"\"For me, productivity is a measure of how much work I do over time...\"","\n**Agnieszka **- ",[964,93362,93363],{},"\"Productivity is for me to do the job as quickly as possible without losing quality.\"","\n**Mateusz **- ",[964,93366,93367],{},"\"Productivity is for me the ability to deliver things in a certain time. \"","\n**Paweł **- ",[964,93370,93371],{},"\"For me, productivity means how quickly and efficiently I can complete a task, especially tasks that are repetitive or very similar. Productivity also includes proper work organisation.\"",[863,93373,93375],{"id":93374},"what-is-your-motivation","What is your motivation?",[842,93377,93378,93379,93360,93384,93387,93388,93368,93393],{},"**Bartosz ",[964,93380,93381,93383],{},[964,93382,2635],{}," \"The greatest motivation for me is the vision of the finished product, other people's successes, and the motive of death, I would like to achieve as much as possible before my last moments\".",[964,93385,93386],{},"\" I am motivated by people with whom I have the opportunity to cooperate when they send good energy, where you can see that they do their job because they like it and 100%. The possibility of self-improvement motivates me, it drives me that I have challenges. I often panic at first, but after a while, I am proud of the progress made. I also recommend the book \"Drive. A completely new perspective on motivation\" by Daniel H. Pink, also in the book \"Management 3.0\" by Jurgen Appelo, raises the issue of motivation.","\n**Mateusz ",[964,93389,93390,93392],{},[964,93391,2635],{}," \"I am motivated by the effect of my work, and I like to make life easier for someone, and that is the greatest motivation for me\".",[964,93394,93395],{},"\"The best motivation for me is the thought of what skills I once had and I will compare it with the present, that is, the thought of developing and not standing still. Something that I couldn't do before, now solves nothing.\"",[863,93397,93399],{"id":93398},"tools-for-organizing-your-work","Tools for organizing your work",[842,93401,93356,93402,93364,93405,93408,93409,93414,93415],{},[964,93403,93404],{},"\"To organize my work, I use a plain sheet of paper where I write down all the things to do. I also use a simple google keep application where I take notes.\"",[964,93406,93407],{},"\"At home I use a large Scrum Board made of a 3-column corkboard to organize my work: It's TO DO, WIP and DONE and I clip on to it with the notes with the saved tasks, it reminds me of many things and makes planning easier.\"","\n**Agnieszka ",[964,93410,93411,93413],{},[964,93412,2635],{}," \"As far as the organization at work is concerned, she uses all kinds of boards, whether in design or physical cards. I also have a planner where I plan the week, but there are more tasks I have to do after work or important work-related events that will affect the second part of my day. I also have a rule that if you work mentally, spend your free time working physically, in my case, most often a bicycle. If you work physically in your free time, learn to read. It allows you to maintain the balance you need.\"","\n**Paweł ",[964,93416,93417,93419],{},[964,93418,2635],{}," \"I organize my working time with the JIRA application, but if I have more things to do, I make a list on an ordinary piece of paper. The list starts with the tiniest ribbons to those for which I need more time. I quickly estimate how much time the list should take me and perform tasks by deleting completed tasks from the list.\"",[863,93421,93423],{"id":93422},"why-is-productivity-important","Why is productivity important?",[842,93425,93426],{},"Productivity allows us to achieve better results in everyday matters, we are more organized, which is a great advantage, because it turns into our achievements at work, where we can show our reliability, but it also helps us to manage our time in our personal life. It is crucial to increase your productivity in all areas of your life.",[842,93428,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":93430},[93431,93432,93433,93434,93435],{"id":93345,"depth":729,"text":93346},{"id":93352,"depth":729,"text":93353},{"id":93374,"depth":729,"text":93375},{"id":93398,"depth":729,"text":93399},{"id":93422,"depth":729,"text":93423},"2020-09-17T00:00:00.000Z","How Bravelab's team defines, measures, and improves productivity. Insights on staying motivated and effective at the workplace.",{"src":93439},"/images/blog/musictechlab_blog_musictechlabs-team-about-productivity.webp",{"enabled":738,"items":93441},[93442,93444,93446],{"text":93443,"icon":50270},"Productivity definitions vary per person but all center on output quality over time.",{"text":93445,"icon":87068},"Tools range from plain paper lists to Google Keep, Scrum boards, and JIRA.",{"text":93447,"icon":4845},"Self-improvement, seeing finished products, and team energy are top motivators.",{},{"title":382,"description":93437},[74615],"qUZ0ur2vkji-LMHRw_KlrxBilcoYhtsKaCrxdKH06Cg",{"id":93453,"title":522,"authors":93454,"badge":723,"body":93459,"category":756,"client":723,"date":93436,"description":93495,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":93496,"keyTakeaways":93498,"meta":93506,"navigation":738,"path":523,"seo":93507,"status":723,"stem":524,"tags":93508,"teaser":723,"__hash__":93509},"posts/blog/software-development/how-to-make-the-first-step-to-establish-a-business-relationship.md",[93455],{"name":93456,"avatar":93457},"Michał Maj",{"src":93458},"/images/people/michal-maj.webp",{"type":725,"value":93460,"toc":93491},[93461,93463,93466,93470,93473,93477,93480,93483,93486,93489],[842,93462,40867],{},[842,93464,93465],{},"If we look at how business works and treat it as a constant flow of things and services that one can provide each other with, we will come to a conclusion that, no matter if those people were doing it consciously or not, their cooperation has led their business to success.\nHowever, it is commonly known that what makes each business really successful is not only goods that could be shared, but most of all these are people. The more people you know, the bigger chance you have to be successful with what you may offer.",[863,93467,93469],{"id":93468},"building-relationships-in-business-during-covid-19-pandemic","Building relationships in business during COVID-19 pandemic",[842,93471,93472],{},"These days, when the pandemic is spreading around the world, and the majority of people are obliged to do lots of their duties remotely, including their own businesses, it seems to be crucial to be an honest and reliable partner who is willing and competent enough to help others with carrying their commercial topics.\nTherefore, not only the quantity of partners is important, but in particular, it is the quality of the relationship between each other. This one may result in long-term cooperation that may be valuable for both sides, both in good and bad times.\nThe main question that comes to mind is: “How to create, at least, the first step to this kind of interaction?”",[863,93474,93476],{"id":93475},"how-to-start-a-business-relationship","How to start a business relationship",[842,93478,93479],{},"t is obvious that it is much easier to create any relationship if the potential partners are connected by someone who is known well for both sides. However, it seems to be much more valuable if cooperation is created from the very beginning.\nDue to the pandemic and the limited access to direct meetings, people are now more obligated to use mostly the tools that allow them to contact others remotely. No matter what kind of them are used, it is crucial to look at them both from the client and sales perspectives. Therefore, let’s have a look at some ways of communication that are the most popular these days.\nFirst of them is the text message that may be sent via email or one of the daily used social communicators. Nowadays, people receive plenty of “written junk” that is fulfilling each mailbox, giving no value at all. Usually, this kind of text is written very generally and, as a result, it seems to be sent to everyone automatically, no matter if the type of client is different or not. To avoid being added to SPAM, the sales side should prepare each message separately by getting, at least, a little bit of knowledge about the chosen prospect. This kind of personalization may give a potential lead feeling that someone is really interested in this particular cooperation. On the other hand, as a reached out person, it would be good to “appreciate” the writer’s effort that was put even in the smallest research. Treating personalized messages as SPAM may result in losing a prospective partner, as well as present and future chances to create any deals that could be valuable for the company.",[842,93481,93482],{},"The second method could be a simple phone call. It is a business tool that some people like and some do not. In comparison to text messages, surely it is much easier and faster to take a handset and get through to each other. On the other hand, it seems to be a solution that is dedicated for people who have, at least, a little bit of business experience. The dynamism of the phone call demands from both sides deep knowledge of how their business works and how it may be changed due to the cooperation they want to begin.\nTherefore, young employees may not be interested in using it frequently unless they know someone better.",[842,93484,93485],{},"Finally, it may be a favorable option to use a combination of the methods mentioned above. This solution may become perfect for someone who does not feel comfortable with choosing a phone call as the first contact but treats it as a more valuable tool. In that case, it would be good to prepare a personalized email as in a normal sales campaign, but including the proposal of handling a video or a phone call. This way gives both sides an opportunity to speak to each other more directly, together with giving them time for better research regarding their companies. The important thing about it is not to postpone the call itself as it may contribute to making the relationship “lazy” and, as a result, not so effective as it could be.",[842,93487,93488],{},"Whatever form of communication is chosen, it is very important to put an effort to make it as personalized as possible. Moreover, it would be a big plus if it fits a particular person, so that such an interlocutor may feel comfortable and, as a result, focus on making a valuable business. Finally, if those factors are connected with transparency and openness for new opportunities, it is highly probable that the first step to create a long-term, business relationship has just been done.",[842,93490,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":93492},[93493,93494],{"id":93468,"depth":729,"text":93469},{"id":93475,"depth":729,"text":93476},"How to start a business relationship through personalized communication. Tips on emails, calls, and video meetings to build lasting partnerships.",{"src":93497},"/images/blog/musictechlab_blog_how-to-make-the-first-step-to-establish-a-business-relationship.webp",{"enabled":738,"items":93499},[93500,93502,93504],{"text":93501,"icon":52136},"Personalized outreach gets far better results than generic mass emails that land in spam.",{"text":93503,"icon":11617},"Phone calls require deeper business knowledge but create stronger initial connections.",{"text":93505,"icon":72284},"Combining a personalized email with a scheduled video call balances comfort and effectiveness.",{},{"title":522,"description":93495},[74615],"vwOXlgzxe9iB_8gy9OBmFfkqwYCdCSE1eD18Nr8C0pE",{"id":93511,"title":598,"authors":93512,"badge":93515,"body":93517,"category":756,"client":723,"date":93436,"description":93598,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":93599,"keyTakeaways":93601,"meta":93609,"navigation":738,"path":599,"seo":93610,"status":723,"stem":600,"tags":93611,"teaser":723,"__hash__":93612},"posts/blog/software-development/performing-saml-sso-using-jwt-in-django.md",[93513],{"name":83061,"avatar":93514},{"src":83063},{"label":93516,"color":32383},"Recommended",{"type":725,"value":93518,"toc":93593},[93519,93521,93524,93527,93530,93533,93547,93550,93554,93557,93560,93563,93566,93571,93577,93580,93583,93586,93588,93591],[842,93520,40867],{},[842,93522,93523],{},"When the Django application needs to be separated into front-end and back-end, and you want to authenticate your calls to your other platforms/services, the stateless JWT in pair with Django Rest Framework is a good choice.\nBut what if you want to integrate single sign-on/single log-out with the other applications which are using SAML? Moreover, your application may be Service Provider and Identity Provider at the same time.",[842,93525,93526],{},"SAML (Security Assertion Markup Language) is an open standard that allows you to perform single sign-on (SSO), namely secure log in to third-party applications using session from another application. That means you don’t need to enter your credentials to authenticate some sites (Service Providers) if you once logged in to the particular site (Identity Provider).",[842,93528,93529],{},"There are many libraries on the internet that allow us to easily integrate the Django authentication mechanism with the SAML, but all of them are based on the standard Django’s session-based authentication, but you can’t use that. Therefore, you need some adapter between JWT → SAML and vice versa SAML → JWT. Have a look at the illustration below:",[842,93531,93532],{},"Libraries used in the project:",[958,93534,93535,93538,93541,93544],{},[961,93536,93537],{},"djangorestframework – Rest framework",[961,93539,93540],{},"django-rest-framework-simplejwt – JWT Auth",[961,93542,93543],{},"djangosaml2 – SAML Service Provider",[961,93545,93546],{},"djangosaml2idp – SAML Identity Provider",[842,93548,93549],{},"Assuming that the SAML and metadata are configured properly, and you can perform SSO using Django and obtain a session. Let’s start from the part when the Django app acts as a Service Provider.",[863,93551,93553],{"id":93552},"django-app-as-a-service-provider","Django app as a Service Provider",[842,93555,93556],{},"First, you have to create view, to which the user will be redirected after SAML login:",[842,93558,93559],{},"Add this view to urls.py:",[842,93561,93562],{},"And to get redirected to this view, add this line to settings.py",[842,93564,93565],{},"What just happened? Basically, you obtained a JWT token for the authenticated (session-based) user, set it to the cookie, and deleted the session as you didn’t need it. It was easy, wasn't it?",[842,93567,93568],{},[964,93569,93570],{},"To increase security, it’s better to store refresh and auth token in the httpOnly cookie instead of local storage (Be aware that this option closes the XSS vulnerability but opens the CSRF). At the time of writing, library Django-rest-framework-simplejwt doesn’t deliver storing tokens in the cookies, this functionality can be found in one of the pull requests. If you don’t want to use cookie storage, as an option, you can just add these tokens as URL params to the redirected URL, instead of setting them into cookies.",[863,93572,93574],{"id":93573},"django-app-as-an-identity-providerin",[996,93575,93576],{},"Django app as an Identity ProviderIn",[842,93578,93579],{},"this case, things are even easier. Let’s say you have logged in into your site, you have set JWT cookie, and you want to perform IDP-initiated login to the different site. And here you are stuck again because to perform an IDP-initiated login, you have to have a Django session (SSOInitView is using LoginRequiredMixin), but as we are using stateless tokens, from the perspective of Django views, you are not authenticated:",[842,93581,93582],{},"This time you have to do the opposite: Authenticate Django view using JWT token. To implement this in the clean and reusable way, let’s create the view decorator:",[842,93584,93585],{},"Also you can reuse this decorator to decorate the SLO view in the same way.",[863,93587,18681],{"id":18680},[842,93589,93590],{},"As you can see, The solution turned out to be easier than it seemed at first. And if the library doesn't provide needed functionality out of the box, it doesn't mean that you have to dig and rewrite everything. You can just do the adapter:)",[842,93592,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":93594},[93595,93596,93597],{"id":93552,"depth":729,"text":93553},{"id":93573,"depth":729,"text":93576},{"id":18680,"depth":729,"text":18681},"A step-by-step guide to implementing SAML SSO authentication with JWT tokens in Django, enabling single sign-on across multiple services and identity providers.",{"src":93600},"/images/blog/musictechlab_blog_performing-saml-sso-using-jwt-in-django.webp",{"enabled":738,"items":93602},[93603,93605,93607],{"text":93604,"icon":7495},"Django can act as both SAML Service Provider and Identity Provider simultaneously.",{"text":93606,"icon":7498},"A thin JWT-to-SAML adapter bridges stateless token auth with session-based SAML login.",{"text":93608,"icon":3847},"Store JWT in httpOnly cookies to prevent XSS, but be aware this opens CSRF exposure.",{},{"title":598,"description":93598},[18784,15279],"fNBRD2TFN_Yyu-OzltAxeXbzCY8Ui6iBsLmV9PuMNac",{"id":93614,"title":490,"authors":93615,"badge":723,"body":93618,"category":756,"client":723,"date":93711,"description":93712,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":93713,"keyTakeaways":723,"meta":93715,"navigation":738,"path":491,"seo":93716,"status":723,"stem":492,"tags":93717,"teaser":723,"__hash__":93718},"posts/blog/software-development/how-repetitive-tasks-impact-your-business.md",[93616],{"name":90672,"avatar":93617},{"src":90674},{"type":725,"value":93619,"toc":93706},[93620,93622,93625,93629,93632,93635,93639,93648,93663,93671,93694,93698,93701,93704],[842,93621,40867],{},[842,93623,93624],{},"The ability to learn, and to be creative was essential for our ancestors to survive and dominate the world. As is today for the success of businesses and organizations. But when there is no challenge or problem-solving involved, then repetition is the weapon of the Dark Side of the Force. People become frustrated, unmotivated, and it negatively impacts entire organizations.\nFortunately, there are ways to deal with negative repetition aspects. Among them is automation, ubiquitous in today’s life. And we can easily build our own automation solutions for digital tasks.",[863,93626,93628],{"id":93627},"the-importance-of-repetition","The importance of repetition",[842,93630,93631],{},"As Zig Ziglar stated, “Repetition is the mother of learning, the father of action, which makes it the architect of accomplishment”. People learn through repetition. It builds paths in our brains. Every time we repeat something, use some skill or knowledge, we strengthen the connection to it, which helps us remember it even after many years.",[842,93633,93634],{},"Do you remember learning to ride a bike, drive a car, or speaking a second language? In the beginning, it is hard, sometimes even painful. But practice makes perfect. And repetition over time is essential.\nLikewise, with repetitions over the years, people learn performing brain surgery, designing complex systems, managing complicated processes, and dealing with difficult people. Yes, even rocket science and theoretical physics! Repetition is the architect of our life.\nBut it also has its dark side. Where “mindless zombies” are born.",[863,93636,93638],{"id":93637},"mindless-zombies-the-dark-side-of-repetition","“Mindless zombies” - the dark side of repetition",[842,93640,93641,93642,93647],{},"As we briefly described, repetition plays a crucial role in our life. But it also has the other side. There are repetitive tasks, which turn us into “mindless zombies”. When there is no challenge or problem-solving involved or when the tasks we are repeating aren’t benefiting creativity and productivity. Then we become jaded, frustrated, as well as we lose our time, energy, and enthusiasm. Do you remember ",[846,93643,93646],{"href":93644,"rel":93645},"https://www.youtube.com/watch?v=DfGs2Y5WJ14",[850],"Charlie Chaplin"," in factory work? Nobody likes repetition tasks like this.",[842,93649,93650,93651,93656,93657,93662],{},"Dull tasks also have a huge impact on businesses. It seems business owners and managers are much more conscious of its negative impact than in Chaplin’s times. But still, according to the data from SnapLogic, ",[846,93652,93655],{"href":93653,"rel":93654},"https://www.information-age.com/productivity-pains-90-workers-repetitive-tasks-123468116/",[850],"90%"," of workers are burdened with boring and repetitive tasks. And the Kofax study shows us that ",[846,93658,93661],{"href":93659,"rel":93660},"https://www.kofax.com/-/media/Files/E-books/EN/eb_ten-ways-manual-tasks-are-costing-your-business_en.pdf",[850],"22%"," of an employee’s time is spent on repetitive tasks.\nPeople in the organization, who are struggling with boring and dull tasks, are not just unhappy. With growing frustration and decreasing the motivation of employees, the company is losing money and becoming less competitive.",[842,93664,88789,93665,93670],{},[846,93666,93669],{"href":93667,"rel":93668},"https://www.impactmybiz.com/blog/blog-7_ways_repetitive_tasks_tech_budget/",[850],"impactmybix.com",", there are several factors on how dull tasks are eating the company’s budget:",[958,93672,93673,93676,93679,93682,93685,93688,93691],{},[961,93674,93675],{},"increased probability of human errors,",[961,93677,93678],{},"reduced productivity,",[961,93680,93681],{},"increased labor cost,",[961,93683,93684],{},"unpredictable workflows,",[961,93686,93687],{},"compliance risks,",[961,93689,93690],{},"less agile business,",[961,93692,93693],{},"lack of visibility into processes.",[863,93695,93697],{"id":93696},"is-there-hope-for-mindless-zombies","Is there hope for “mindless zombies”?",[842,93699,93700],{},"Repetition is the cornerstone of learning, the most important skill for mankind, but it also has the dark side. Negative repetition turns people into “mindless zombies”, destroys their happiness and enthusiasm, as well as negatively impacts businesses and organizations.",[842,93702,93703],{},"In the next article in this series, I am going to elaborate on how to deal with the dark side of repetition, and introduce Zapier, as a solution for automating digital tasks. In the following part, I will also show a use case, how we use Zapier in Bravelab, and present the process of building basic integration.",[842,93705,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":93707},[93708,93709,93710],{"id":93627,"depth":729,"text":93628},{"id":93637,"depth":729,"text":93638},{"id":93696,"depth":729,"text":93697},"2020-09-07T00:00:00.000Z","Repetitive tasks are an inherent part of our work. However, they decrease our effectiveness and have an impact on business objectives and performance measures.",{"src":93714},"/images/blog/musictechlab_blog_how-repetitive-tasks-impact-your-business.webp",{},{"title":490,"description":93712},[74615],"pTdO4pZea1CgsLgIZKh5NMl-mE0yt-7R6F73M8hRWMg",{"id":93720,"title":462,"authors":93721,"badge":723,"body":93726,"category":756,"client":723,"date":93793,"description":93794,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":93795,"keyTakeaways":93797,"meta":93807,"navigation":738,"path":463,"seo":93808,"status":723,"stem":464,"tags":93809,"teaser":723,"__hash__":93810},"posts/blog/software-development/factors-that-contribute-to-the-success-or-failure-of-an-it-outsourcing-project.md",[93722],{"name":93723,"avatar":93724},"Agnieszka Warżała",{"src":93725},"/images/people/agnieszka-warzala.webp",{"type":725,"value":93727,"toc":93789},[93728,93730,93733,93737,93740,93743,93760,93764,93767,93784,93787],[842,93729,40867],{},[842,93731,93732],{},"Before making a decision about outsourcing software development work, you should consider the factors that have an impact on the success or failure of the project.",[863,93734,93736],{"id":93735},"what-should-be-considered-in-software-development-outsourcing","What should be considered in software development outsourcing?",[842,93738,93739],{},"Not only choosing the right service provider, whose experience and skills meet the project requirement, is crucial, but knowing the factors that have an impact on the outcome of the outsourcing project can help you effectively collaborate with external companies. What is more, the perception and attitude of both clients and suppliers are highly relevant in outsourcing projects.",[842,93741,93742],{},"The factors that contribute to the success of the outsourcing project are:",[958,93744,93745,93748,93751,93754,93757],{},[961,93746,93747],{},"Communication between the client and the service provider, as well as inside the service provider team - is one of the most significant success factors. Open and transparent communication can help you build trust and establish business relationships with your partner to gain mutual benefits.",[961,93749,93750],{},"Working according to the transition plan - in order to effectively move the project from the implementation to the maintenance phase, the team should perform the tasks and activities included in the project transition plan.",[961,93752,93753],{},"Managing the business case for outsourcing - developing a business case for an outsourcing solution or initiative helps fully understand the short- and long-term advantages resulting from the outsourcing strategy. The business case allows evaluating the risks associated with the investment in an outsourcing initiative.",[961,93755,93756],{},"Demand management - the service provider usually takes over the managing functions in the outsourced projects. Proper planning of the demand for project coordination has an impact on the outcome of the outsourced project.",[961,93758,93759],{},"Vendor’s empathy - it is the capability of the service provider to put himself in the position of his client. The understanding of the client's needs, demands, and resources help the service provider to better manage the project.",[863,93761,93763],{"id":93762},"what-has-a-negative-impact-on-outsourcing-software-development-projects","What has a negative impact on outsourcing software development projects?",[842,93765,93766],{},"Nevertheless, not all outsourcing projects are successful. One of the most common problems arising in the outsourcing projects are related to communication:",[958,93768,93769,93772,93775,93778,93781],{},[961,93770,93771],{},"Deferred replies - in the outsourcing projects, there is usually the location distance between the client and a vendor. Therefore, late replies to emails and deferred calls may interrupt the effectiveness of the teamwork and development process.",[961,93773,93774],{},"Deficiency of casual correspondence that is occasional and controlled - lack of systematic communication with the service provider might result in a loss of control over the progress of work, as well as prolong the process of solving obstacles that would cause a delay in the project implementation",[961,93776,93777],{},"Non-recorded promises that are done amid videoconferencing or telephone conversation - the missing written summary of telephone or online calls between the customer and the service provider may lead to a conflict between the parties and failure to meet each other's expectations.",[961,93779,93780],{},"Time zone - when one of the parties is located in another time zone, it seems to be problematic in terms of communication and collaboration. However, effective management allows you to work without any interference.",[961,93782,93783],{},"Cultural differences - cultural diversity between cooperating companies can sometimes affect the effectiveness of communication. Nevertheless, dispelling negative stereotypes and learning other cultures helps you understand different perspectives and brings a fresh perspective to your project and work environment.",[842,93785,93786],{},"Despite communication issues, you can also come across other factors that can lead to the failure of the outsourcing project. On the graph are presented the top 10 issues depending on the frequency of occurrence:",[842,93788,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":93790},[93791,93792],{"id":93735,"depth":729,"text":93736},{"id":93762,"depth":729,"text":93763},"2020-08-26T00:00:00.000Z","Key factors that impact the success or failure of software development outsourcing projects and how understanding them improves your strategy.",{"src":93796},"/images/blog/musictechlab_blog_factors-that-contribute-to-the-success-or-failure-of-an-it-outsourcing-project.webp",{"enabled":738,"items":93798},[93799,93801,93803,93805],{"text":93800,"icon":72284},"Communication is the single most significant success factor in outsourcing projects.",{"text":93802,"icon":3847},"Deferred replies and missing meeting summaries are top failure causes.",{"text":93804,"icon":11617},"Vendor empathy, understanding the client's needs and resources, improves outcomes.",{"text":93806,"icon":50270},"A solid transition plan is critical for moving from implementation to maintenance.",{},{"title":462,"description":93794},[74615],"cvfixnNEOXKCaI91dbuY0PdxjAxg5j8PSSj0OF5VaPY",{"id":93812,"title":510,"authors":93813,"badge":723,"body":93816,"category":756,"client":723,"date":93850,"description":93851,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":93852,"keyTakeaways":93854,"meta":93862,"navigation":738,"path":511,"seo":93863,"status":723,"stem":512,"tags":93864,"teaser":723,"__hash__":93865},"posts/blog/software-development/how-to-export-orders-in-saleor-io-to-xlsx-file.md",[93814],{"name":90772,"avatar":93815},{"src":90774},{"type":725,"value":93817,"toc":93848},[93818,93820,93823,93826,93840,93843,93846],[842,93819,40867],{},[842,93821,93822],{},"Running a successful e-commerce site requires constantly monitoring sales performance. The ability to export data for analyzing and visualization in popular formats that can be shared is crucial. The simplest way is to export data in CSV format that can be read by plethora of tools. However if there is a need to control how data is presented, using format that embeds formating alongside is better. Exporting data in Excel XLSX format allows changing font family, font colors, columns size, compute values using formulas and so on. While the Saleor.io team is working on adding the ability to export various elements in CSV, we can build our own XLS exporter.",[842,93824,93825],{},"The Saleor.io dashboard uses GraphQL, which means you cannot return data in another format than JSON. Instead of returning the XLS file, we can save the file on disk and return the link for download. Another way is to skip the GraphQL part and add a new view that returns the generated file.",[842,93827,93828,93829,1589,93832,93835,93836,93839],{},"There two main packages to choose from: ",[964,93830,93831],{},"openpyxl",[964,93833,93834],{},"xlsxwriter"," (and ",[964,93837,93838],{},"xlwt"," if there is a need for an older binary format xls). Each package has its pros and cons but general usage is the same. Create a workbook, add a worksheet to it and populate worksheet with data. Then save the file on disk or return it in HTTP response.",[842,93841,93842],{},"First, install a package that adds XLSX support to python.",[842,93844,93845],{},"After that, create a route for this view.",[842,93847,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":93849},[],"2020-08-04T00:00:00.000Z","A step-by-step guide to exporting orders from the Saleor.io open-source e-commerce platform to XLSX files using Python packages like openpyxl or xlsxwriter.",{"src":93853},"/images/blog/musictechlab_blog_how-to-export-orders-in-saleor-io-to-xlsx-file.webp",{"enabled":738,"items":93855},[93856,93858,93860],{"text":93857,"icon":2895},"Saleor GraphQL returns JSON only, so XLSX export requires a custom Django view.",{"text":93859,"icon":5365},"Python packages openpyxl and xlsxwriter both create workbooks with similar APIs.",{"text":93861,"icon":7495},"Always add JWT permission checks to protect export endpoints from unauthorized access.",{},{"title":510,"description":93851},[52276,18784],"MuJLnKEKQcN5VvY1Vio9UVIODhRFSrt_v6cRK2AIobM",{"id":93867,"title":526,"authors":93868,"badge":93871,"body":93872,"category":756,"client":723,"date":93850,"description":93957,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":93958,"keyTakeaways":93960,"meta":93968,"navigation":738,"path":527,"seo":93969,"status":723,"stem":528,"tags":93970,"teaser":723,"__hash__":93971},"posts/blog/software-development/how-to-manage-tenants-in-the-multitenant-app-based-on-django-tenants-and-saleor-io-platform.md",[93869],{"name":90772,"avatar":93870},{"src":90774},{"label":93516,"color":32383},{"type":725,"value":93873,"toc":93951},[93874,93876,93879,93883,93886,93889,93900,93903,93906,93917,93924,93928,93931,93935,93938,93941,93943,93946,93949],[842,93875,40867],{},[842,93877,93878],{},"Saleor is a great e-commerce open-source platform for building modern online stores. Uses Django framework and GraphQL to deliver a robust backend for creating PWA stores. Enhancing Saleor.io with multi-tenant abilities allows developing a SaaS platform that can manage multiple online stores from one place.",[863,93880,93882],{"id":93881},"evaluating-multi-tenant-architecture","Evaluating multi-tenant architecture",[842,93884,93885],{},"Django framework is a mature framework with many packages that can add multitenancy. There are two main points to evaluate when building such a solution: how to store tenants’ data and which package to use.",[842,93887,93888],{},"Storing data for multiple tenants can be achieved in three different ways:",[958,93890,93891,93894,93897],{},[961,93892,93893],{},"create one database per tenant",[961,93895,93896],{},"create one schema per tenant",[961,93898,93899],{},"have all tenants share the same table(s)",[842,93901,93902],{},"Each solution has its pros and cons. There are many publications that discuss this in detail. Depending on how many tenants we are expecting or how much customization tenants need, we can choose one that suits us best. In our case, we have chosen “one schema per tenant”, which is a sensible middle ground.",[842,93904,93905],{},"Choosing a package requires evaluating:",[958,93907,93908,93911,93914],{},[961,93909,93910],{},"does it support chosen architecture",[961,93912,93913],{},"is it production-ready",[961,93915,93916],{},"does it have documentation",[842,93918,93919,93920,93923],{},"We have chosen ",[964,93921,93922],{},"django_tenants"," package as it meets all our needs. It’s using database schemas to save tenants’ data, is under active development with a stable release cycle, and has comprehensive documentation.",[863,93925,93927],{"id":93926},"how-does-it-work","How does it work?",[842,93929,93930],{},"Every tenant has its own schema in the database. There is also one public schema for storing admin and shared apps. Tenants are identified by their hostnames. Multi-tenant middleware analyzes every request and matches the tenant's hostname with their schema. Then it sets a connection to use the tenant’s schema and all queries are run in the tenant context. If no tenant is found, a 404 error is raised.",[863,93932,93934],{"id":93933},"how-to-build-an-admin-panel","How to build an admin panel",[842,93936,93937],{},"Writing a custom admin panel can be a daunting task requiring writing a lot of boilerplate code for CRUD operations. Instead, we can use Django to build an admin interface from models. It allows us to concentrate on adding additional functionality. The default tenant model contains only information about schema and domains associated with it. First, we can add fields for storing some more useful information about our tenants like their names, contact information, and activation status.",[863,93939,93934],{"id":93940},"how-to-build-an-admin-panel-1",[842,93942,93937],{},[842,93944,93945],{},"from django_tenants.middleware.main import TenantMainMiddleware\n‍",[842,93947,93948],{},"Adding multitenancy to Saleor.io platform is not a complicated task. By choosing the right tools and utilizing the power of the Django admin panel, we are able to create a good starting point for building a multi-tenant ecommerce platform.",[842,93950,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":93952},[93953,93954,93955,93956],{"id":93881,"depth":729,"text":93882},{"id":93926,"depth":729,"text":93927},{"id":93933,"depth":729,"text":93934},{"id":93940,"depth":729,"text":93934},"Saleor is a great e-commerce open-source platform for building modern online stores with a multi-tenant management system.",{"src":93959},"/images/blog/musictechlab_blog_how-to-manage-tenants-in-the-multitenant-app-based-on-django-tenants-and-saleor-io-platform.webp",{"enabled":738,"items":93961},[93962,93964,93966],{"text":93963,"icon":1769},"One database schema per tenant provides a good balance of isolation and simplicity.",{"text":93965,"icon":1779},"django_tenants middleware routes each request to the correct tenant schema by hostname.",{"text":93967,"icon":3844},"Django admin can display cross-tenant metrics like order counts using schema context managers.",{},{"title":526,"description":93957},[52276,18784],"NMdamhu_x89sGQ9HE62HQbv5evHZQVLtKFw3MjCfsIE",{"id":93973,"title":124,"authors":93974,"badge":723,"body":93979,"category":731,"client":723,"date":94013,"description":94014,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":94015,"keyTakeaways":94017,"meta":94025,"navigation":738,"path":125,"seo":94026,"status":723,"stem":126,"tags":94027,"teaser":723,"__hash__":94028},"posts/blog/music-data/data-modeling-in-mongodb-with-the-usage-of-design-patterns.md",[93975],{"name":93976,"avatar":93977},"Bartosz Wilk",{"src":93978},"/images/people/bartosz-wilk.webp",{"type":725,"value":93980,"toc":94009},[93981,93984,93988,93991,93994,93997,93999,94007],[842,93982,93983],{},"Using NoSQL databases is slightly different than using primary relational databases like PostgreSQL. You have more freedom in data modeling of your documents cause each document of a collection can contain diversified properties. In larger projects, this advantage can lead to making a little mess in your data. Such as in the programming case, design patterns can help you organize your relations between documents and collections. There are many patterns that you can choose during data modeling in MongoDB, but in this article, you get to know about three of them.",[863,93985,93987],{"id":93986},"attribute-pattern","Attribute Pattern",[842,93989,93990],{},"When you have many documents mostly with the same properties, but they also have their specific attributes, this pattern can be useful for you. All you need to do is create one array property for all these characteristics and put them into as key-value objects. This solution will enable you to sort and search documents in an easier way and it will improve the performance. You can use it, eg., in an e-commerce project where you have a lot of similar products.",[842,93992,93993],{},"This kind of pattern, as the name suggests, makes a tree of dependencies between documents of a collection. There are several approaches to make a tree, but the easiest is to add properties with references to a parent and children of a document. This design pattern will be useful everywhere where there is a hierarchical data structure, eg., dependencies between employees in an organization.",[842,93995,93996],{},"Last but not least, the design pattern can improve the performance of your application if you often make JOIN operations on your documents. To reduce them, you need to embed the most necessary properties of a document of the second collection into a document of the first one. In this way, you won't have to make JOIN operations so often, and if you need more properties of the document of the second collection, you always have access to take them.",[863,93998,25699],{"id":8196},[842,94000,94001,94002,861],{},"To sum up, these three patterns, I described shortly, can improve the organization of your data structure and optimize the performance of your application. I encourage you to read the MongoDB documentation to get to know more. If you want to explore more design patterns, I recommend you to join the data modeling course on ",[846,94003,94006],{"href":94004,"rel":94005},"https://university.mongodb.com/",[850],"MongoDB university",[842,94008,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":94010},[94011,94012],{"id":93986,"depth":729,"text":93987},{"id":8196,"depth":729,"text":25699},"2020-07-22T00:00:00.000Z","The most useful data modeling design patterns in MongoDB are attributed, three, and extended reference patterns.",{"src":94016},"/images/blog/musictechlab_blog_data-modeling-in-mongodb-with-the-usage-of-design-patterns.webp",{"enabled":738,"items":94018},[94019,94021,94023],{"text":94020,"icon":2895},"The Attribute Pattern stores varying properties as key-value arrays for easier search and indexing.",{"text":94022,"icon":1769},"The Tree Pattern creates parent-child references for hierarchical data structures.",{"text":94024,"icon":2939},"The Extended Reference Pattern embeds frequently-joined fields to reduce costly JOIN operations.",{},{"title":124,"description":94014},[18784,731,84161],"6bbWNGiAL98eVqA9EG8Vhg2CJRcmJORV5LTnvS_9YiI",{"id":94030,"title":366,"authors":94031,"badge":723,"body":94034,"category":756,"client":723,"date":94080,"description":94081,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":94082,"keyTakeaways":94084,"meta":94092,"navigation":738,"path":367,"seo":94093,"status":723,"stem":368,"tags":94094,"teaser":723,"__hash__":94095},"posts/blog/software-development/bravelab-io-is-recognized-as-a-top-custom-software-developer-by-clutch.md",[94032],{"name":93723,"avatar":94033},{"src":93725},{"type":725,"value":94035,"toc":94078},[94036,94038,94041,94050,94053,94056,94059,94062,94076],[842,94037,40867],{},[842,94039,94040],{},"In 2020, more companies are realizing the benefits of custom software development. It helps address specific issues and can be much more efficient than out-of-the-box software. We’re excited to be a part of this growing industry.",[842,94042,94043,94044,94049],{},"We' re happy to share that we’ve been named a ",[846,94045,94048],{"href":94046,"rel":94047},"https://clutch.co/developers/poland",[850],"Clutch leader."," We’re listed as one of the Top Software Developers in Poland second year in a raw.",[842,94051,94052],{},"Clutch is a leading B2B directory and research platform based in Washington, DC. They’re invested in publishing in-depth client reviews and data-driven content that businesses can access for free.",[842,94054,94055],{},"Since we created our Clutch profile, we’ve received 14 reviews from our clients and boast a near-perfect rating of 4.9/5 stars.",[842,94057,94058],{},"In our most recent review, our client praised our team for their reliability, organization, and strong communication skills. We’re proud of our team for always putting their best foot forward and providing high-quality, unique solutions for our clients.",[842,94060,94061],{},"We’ve been in business for 10 years and we’re happy to see our hard work pay off. We want to take the time to thank all of the clients who took the time to leave us reviews with Clutch. We wouldn’t have received this award without your help.",[842,94063,94064,94065,94070,94071,94075],{},"You can read more about our past clients’ experience on our ",[846,94066,94069],{"href":94067,"rel":94068},"https://clutch.co/profile/musictech-lab",[850],"Clutch profile",". If you have any questions or are interested in our services, don’t hesitate to ",[846,94072,94074],{"href":8661,"rel":94073},[850],"contact us",", we’d be happy to help!",[842,94077,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":94079},[],"2020-07-07T00:00:00.000Z","Clutch.co named Bravelab.io a top custom software development company in Poland in 2020, recognizing us as a top provider for the second year.",{"src":94083},"/images/blog/musictechlab_blog_musictechlab-io-is-recognized-as-a-top-custom-software-developer-by-clutch.webp",{"enabled":738,"items":94085},[94086,94088,94090],{"text":94087,"icon":3920},"Named a Clutch leader and top software developer in Poland for the second consecutive year.",{"text":94089,"icon":4845},"Earned 14 client reviews on Clutch with a near-perfect 4.9/5 star rating.",{"text":94091,"icon":5365},"10 years in business specializing in custom software development.",{},{"title":366,"description":94081},[74615],"qzu1ArqLS9VGZF5xeiPkAxvsRg8K8wjVRAQfbkGCP7A",{"id":94097,"title":582,"authors":94098,"badge":723,"body":94101,"category":756,"client":723,"date":94080,"description":94258,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":94259,"keyTakeaways":94260,"meta":94268,"navigation":738,"path":583,"seo":94269,"status":723,"stem":584,"tags":94270,"teaser":723,"__hash__":94271},"posts/blog/software-development/musictechlab-is-recognized-as-a-top-custom-software-developer-by-clutch.md",[94099],{"name":93723,"avatar":94100},{"src":93725},{"type":725,"value":94102,"toc":94251},[94103,94115,94134,94136,94140,94146,94149,94151,94155,94162,94173,94180,94182,94186,94209,94211,94215,94230,94235,94237,94241],[842,94104,94105,94106,94110,94111,94114],{},"We're proud to share that MusicTech Lab has been named a ",[846,94107,94109],{"href":94046,"rel":94108},[850],"Clutch leader"," — listed as one of the ",[996,94112,94113],{},"Top Software Developers in Poland"," for the second consecutive year.",[1045,94116,94118,94122,94126,94130],{"className":94117},[1048,50574,50605,1051,1052],[1054,94119],{"description":94120,"icon":52092,"title":94121},"Average Clutch rating across all client reviews","4.9 / 5.0",[1054,94123],{"description":94124,"icon":4830,"title":94125},"Verified client reviews published on Clutch.co","33+ Reviews",[1054,94127],{"description":94128,"icon":65185,"title":94129},"In business delivering custom software solutions","10+ Years",[1054,94131],{"description":94132,"icon":7555,"title":94133},"Named Top Developer in Poland two years running","2x Recognized",[4937,94135],{},[863,94137,94139],{"id":94138},"what-is-clutchco","What is Clutch.co?",[842,94141,94142,94145],{},[846,94143,89362],{"href":89360,"rel":94144},[850]," is a leading B2B directory and research platform based in Washington, DC. They publish in-depth, verified client reviews and data-driven content that businesses can access for free. Companies are ranked based on the quality and quantity of their reviews, their market presence, and their ability to deliver results.",[842,94147,94148],{},"To rank highly on the platform, firms must earn top-notch reviews from real clients — demonstrating expertise, communication, and the quality of their work.",[4937,94150],{},[863,94152,94154],{"id":94153},"what-our-clients-say","What Our Clients Say",[41054,94156,94157],{},[842,94158,94159],{},[964,94160,94161],{},"\"The team's reliability, organization, and strong communication skills made them a pleasure to work with. They consistently put their best foot forward and delivered high-quality, unique solutions.\"",[41054,94163,94164,94169],{},[842,94165,94166],{},[964,94167,94168],{},"\"The team consisted of three experienced and highly motivated developers. We had frontend and backend developers, and we were working closely with UI/UX designers.\"",[842,94170,94171,87520],{},[996,94172,87519],{},[41054,94174,94175],{},[842,94176,94177],{},[964,94178,94179],{},"\"They are very engaged in a client's project, meaning that they will go the extra mile if needed. Working with them as an outside development vendor has been almost as seamless as with an in-house team.\"",[4937,94181],{},[863,94183,94185],{"id":94184},"our-services","Our Services",[1045,94187,94189,94192,94195,94198,94202,94206],{"className":94188},[1048,1049,1765,52080,1051,1052],[1054,94190],{"description":94191,"icon":5365,"title":89692},"Full-stack web and mobile applications built from scratch — Python, Django, Nuxt, Flutter.",[1054,94193],{"description":94194,"icon":4855,"title":89465},"Cross-platform iOS and Android apps using Flutter with a single codebase.",[1054,94196],{"description":94197,"icon":4845,"title":87402},"Embedded developers who integrate seamlessly with your existing team.",[1054,94199],{"description":94200,"icon":9547,"title":94201},"Specialized tools for the music industry — metadata, streaming, royalties, and audio.","Music Tech Solutions",[1054,94203],{"description":94204,"icon":2917,"title":94205},"Scalable infrastructure on AWS and GCP with CI/CD pipelines and monitoring.","Cloud & DevOps",[1054,94207],{"description":94208,"icon":5507,"title":89471},"User research, wireframing, and interface design that drives engagement.",[4937,94210],{},[863,94212,94214],{"id":94213},"why-this-recognition-matters","Why This Recognition Matters",[1045,94216,94218,94222,94226],{"className":94217},[1048,1049,1050,1051,1052],[1054,94219],{"description":94220,"icon":1057,"title":94221},"Every Clutch review is independently verified — you're reading real feedback from real clients.","Verified Trust",[1054,94223],{"description":94224,"icon":1067,"title":94225},"Being listed as a top developer in Poland puts us on the radar for international clients seeking quality engineering.","Global Visibility",[1054,94227],{"description":94228,"icon":5512,"title":94229},"This award wouldn't exist without our clients. Their trust and feedback drive everything we do.","Client Relationships",[1032,94231,94232],{},[842,94233,94234],{},"We've been in business for over a decade and delivered 90+ successful projects. This recognition is a milestone — but the real reward is the long-term partnerships we've built along the way.",[4937,94236],{},[863,94238,94240],{"id":94239},"see-for-yourself","See for Yourself",[1045,94242,94244,94248],{"className":94243},[13033,50238,50239,1052],[50241,94245],{"color":50243,"icon":94246,"label":94247,"to":94067,"variant":50246,"target":50245},"i-lucide-external-link","View Our Clutch Profile",[50241,94249],{"color":50243,"icon":72284,"label":94250,"to":3802,"variant":84504},"Start a Conversation",{"title":728,"searchDepth":729,"depth":729,"links":94252},[94253,94254,94255,94256,94257],{"id":94138,"depth":729,"text":94139},{"id":94153,"depth":729,"text":94154},{"id":94184,"depth":729,"text":94185},{"id":94213,"depth":729,"text":94214},{"id":94239,"depth":729,"text":94240},"Clutch.co named MusicTech Lab a top custom software development company in Poland, recognizing us as a leading provider for the second consecutive year.",{"src":94083},{"enabled":738,"items":94261},[94262,94264,94266],{"text":94263,"icon":3920},"MusicTech Lab earned a 4.9/5.0 Clutch rating with 33+ verified client reviews.",{"text":94265,"icon":1067},"Named Top Software Developer in Poland by Clutch for two consecutive years.",{"text":94267,"icon":37696},"Over 90 successful projects delivered across 10+ years of custom software development.",{},{"title":582,"description":94258},[74615],"66yaSND3KkmWHn3gjcqKbSIYvzy2rt6FkvdfXDhgi5I",{"id":94273,"title":390,"authors":94274,"badge":723,"body":94278,"category":756,"client":723,"date":94362,"description":94363,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":94364,"keyTakeaways":94366,"meta":94376,"navigation":738,"path":391,"seo":94377,"status":723,"stem":392,"tags":94378,"teaser":723,"__hash__":94379},"posts/blog/software-development/bravely-app-how-to-be-more-productive-with-django-quick.md",[94275],{"name":90132,"avatar":94276},{"src":94277},"/images/people/szymon-zmilczak.webp",{"type":725,"value":94279,"toc":94353},[94280,94282,94285,94289,94292,94296,94299,94303,94306,94310,94313,94317,94324,94328,94331,94334,94341,94345,94348,94351],[842,94281,40867],{},[842,94283,94284],{},"We wanted to automate some of the internal processes and store different data in one place. The idea was to create a simple tool that would assist our employees with their operational tasks. The main goal was to build and deliver the solution quickly, without unnecessary fireworks.",[863,94286,94288],{"id":94287},"how-to-design-such-a-project","How to design such a project?",[842,94290,94291],{},"Python with django was chosen as a main technology simply due to its simplicity and ability to easily “hack” things together. It turned out nice, as virtually every developer in our company knows some python and as a result, anyone can do a fix or add a minor feature to the project. We decided to build a monolithic django application which leverages django’s admin heavily as it is the fastest way (known to us) to have the project running. It’s not really a good choice for a big service, but hey - we were not planning on writing an enterprise CRM.",[863,94293,94295],{"id":94294},"how-to-develop-such-a-project","How to develop such a project?",[842,94297,94298],{},"Thanks to our knowledge of the tools we were working with, we had two project instances running in a matter of days. We started with a simple stack: Django + PostgreSQL + Nginx running in Docker on a VPS. Of course, we had a CI/CD pipeline running since day one, and we utilized it heavily. Thanks to the pipeline, two environments (staging and production) and an agile approach to the development, we had a quick feedback loop between developers and the users. The features were discussed, implemented, deployed, and approved in a few hours. After about two weeks of development, we started to retire previously used tools that had equivalents in this project - that includes complex spreadsheets, Jira plugins, and a couple of external services.",[863,94300,94302],{"id":94301},"how-to-keep-developing-such-a-project","How to keep developing such a project?",[842,94304,94305],{},"To ensure the project reliability and code quality, we introduced some strict rules. From the beginning, all tasks needed to be selected for development before the work on it started and approved after it was deployed. This approach allowed us to keep our kanban board up to date and as close to reality as possible.\nOn the development part, all pull requests, apart from needing to be code reviewed (duh), needed to pass a couple of checks that were enforced by the CI pipeline. First, a static analysis was run using flake8 - it caught many of the simple issues. Next, project formatting was checked by a tool called black - this step ensured the whole project is always formatted in the same convention. Further,Unit tests were run which verified that the code works as intended. Last, a minimal viable environment was spun up, and integration tests were run on it to prevent unexpected failure. After a pull request was merged, the code was automatically deployed to a staging environment. A deployment to production could be requested by anyone and was automatically performed when a code owner approved it.",[863,94307,94309],{"id":94308},"how-to-maintain-such-a-project","How to maintain such a project?",[842,94311,94312],{},"With quick deployment comes lots of bugs and unexpected failures. First thing that we can do is to introduce the prevention tools described in a previous chapter. Unfortunately, they can’t detect 100% of the bugs because the world would be too simple. Therefore, the next thing that we can do is detection and alerting. For that, we used sentry - a quite popular tool that gathers logs and tracebacks from your application. It has integrations with virtually all languages and frameworks. Sentry allows you to detect failures in your application, alert you of new issues, estimate their scope, and simplify debugging and fixing them. A simple tool like a website uptime monitor will help you detect the most serious failures, i.e., server reboot, deployment crash, compute provider downtime.\nAnother thing that can be done to raise project reliability is to use automated failure recovery mechanisms. We started using Kubernetes to take advantage of its ability to monitor running service. In the first step, we added health checks to all services in the project. It allowed the Kubernetes to detect crashed or unhealthy containers and restart them. The second thing that we did was to leverage Kubernetes’s load balancing capabilities to implement zero-downtime deployment. That allowed decoupling developer deployments from users that previously needed to be asked to save their work and pause working for a couple of minutes. With the help of some simple tools (Argo cd & workflows), Kubernetes can be made to automatically rollback your service to a previous version in case of a faulty deployment. Mind that we are still running a small application on a cheap VPS, cloud-based enterprise solution.",[863,94314,94316],{"id":94315},"how-to-increase-quality-of-life","How to increase quality of life?",[842,94318,94319,94320,94323],{},"We implemented a couple of things that hugely increased our quality of life with this project. One of those things was a Django application that manages our backups. It exposed a user interface via classic Django admin to list, create, and delete backups of our media files and persistent databases. It also allows configuring when backups are made and where they are stored. At the moment, we are making a database backup every 4h and store them locally for a month, and we do a full system backup every week and store it indefinitely on AWS S3. Such a simple application that allows setting up, reviewing, and manually creating backups, greatly simplifies the backup flow and access control (it uses the Django permissions framework). Side note: please, make and audit your backups. If you are waiting for a signal from the world, this is it.\nAnother thing that simplifies your and your users’ life is enabling oauth2 login. Allow your application users to login with your organisation accounts - no extra login and password make everything more secure and simpler to use. Don’t actually write it yourself - it might be difficult to ensure security and there are a ton of existing and proven solutions that can be used. Just search for oauth django packages.\nTo allow any developer to start implementing features in the project without bothering someone for hours about how to set up a local environment, have a comprehensive readme written. Describe how to do that on your organisation's most popular operating systems. List prerequisites (e.g. git client, python interpreter - nothing is obvious), preferably with their versions that are known to work. Describe how to populate the database",[1086,94321,94322],{},"s"," or how to connect to a populated one. Briefly describe the conventions and what is (and should be) placed wherein the repository. You should also mention what big parts of the system are and what they do. Consider pointing out some tricky concepts or hacks that might be misunderstood. A good readme saves a lot of time and headache but doesn’t mistake it for proper documentation.",[863,94325,94327],{"id":94326},"what-it-actually-does","What it actually does?",[842,94329,94330],{},"The biggest module in our project is used to manage employees. It stores all information about our current and past employees and presents it in many different ways. This includes generating CVs for our clients, graphing rates against a skill level, making sure the profile is up to date, counting used and available days off, and many more. This application also ensures that most paperwork is done on time.",[842,94332,94333],{},"Maybe not so big, but an incredibly useful module is a cash flow one. It gets all our invoices from the external system and compares them against financial statements from the accounting team. It also creates a revenue and cost prediction for the upcoming months based on previous data and projects statuses from the sales module. It also alerts us about overdue invoices.",[842,94335,94336,94337,94340],{},"One more worth mentioning module is an inventory management one. It is straightforward as it only stores “items” that can be assigned ",[1086,94338,94339],{},"or not"," to particular employees. It allows us to keep track of how many of a given asset we have available and who “has” what. We store information mostly about the big stuff like assigned laptops and displays.",[863,94342,94344],{"id":94343},"what-is-the-future","What is the future?",[842,94346,94347],{},"At the moment, we are focusing on extending the sales module, and we have planned a couple of extra features. This project started as and still is our internal-only platform, but more and more of our partners are interested in using it or at least parts of it. We are considering cleaning it up a bit and making it less tailor-made for us to release it as a PaaS.",[842,94349,94350],{},"Feel free to contact us via the contact form if you are interested or if you have any questions.",[842,94352,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":94354},[94355,94356,94357,94358,94359,94360,94361],{"id":94287,"depth":729,"text":94288},{"id":94294,"depth":729,"text":94295},{"id":94301,"depth":729,"text":94302},{"id":94308,"depth":729,"text":94309},{"id":94315,"depth":729,"text":94316},{"id":94326,"depth":729,"text":94327},{"id":94343,"depth":729,"text":94344},"2020-04-24T00:00:00.000Z","How we built a custom data management tool with Django, Kubernetes, and Celery to automate internal processes and boost team productivity.",{"src":94365},"/images/blog/musictechlab_blog_bravely-app-how-to-be-more-productive-with-django-quick.webp",{"enabled":738,"items":94367},[94368,94370,94372,94374],{"text":94369,"icon":2939},"Django admin enabled a working internal tool within two weeks of development.",{"text":94371,"icon":37696},"CI/CD pipeline with staging and production ran from day one.",{"text":94373,"icon":7495},"Kubernetes health checks and zero-downtime deployments improved reliability.",{"text":94375,"icon":2895},"Automated database backups every 4 hours with weekly full backups to S3.",{},{"title":390,"description":94363},[18784],"Cgxn0oEkfuaiJ-nWd6Fh_JR499EQJXEv8AOnzXwrZBY",{"id":94381,"title":606,"authors":94382,"badge":723,"body":94387,"category":756,"client":723,"date":94428,"description":94429,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":94430,"keyTakeaways":94432,"meta":94440,"navigation":738,"path":607,"seo":94441,"status":723,"stem":608,"tags":94442,"teaser":723,"__hash__":94443},"posts/blog/software-development/recruitment-system-integrating-gmail-bravely-jira-slack-and-copper-crm.md",[94383],{"name":94384,"avatar":94385},"Paweł Juraszek",{"src":94386},"/images/people/pawel-juraszek.webp",{"type":725,"value":94388,"toc":94424},[94389,94391,94394,94396,94399,94407,94410,94413,94416,94419,94422],[842,94390,40867],{},[842,94392,94393],{},"The recruitment process is complex and consists of many steps. In our system, new candidates can apply for a job position through: various external sites, dedicated email addresses, the form on our website, or any other available way.\nAcquiring candidates from many sources is problematic and time-consuming. Creating one aggregated email address solves only part of the problem, especially if it aggregates other things as well, for example, notifications. Therefore, it is important to create a system that can improve effectiveness and shorten the time needed to manage the application process.",[863,94395,27663],{"id":52066},[842,94397,94398],{},"The idea was to integrate various APIs, such as Cooper CRM, Atlassian Jira, Google Gmail, and G Suite. Messages from all sources are aggregated in one email address, where they wait to be processed with our module. The task of checking the inbox is run every 15 minutes.",[842,94400,94401,94402,94406],{},"Aggregation of mailboxes may cause a massive amount of messages in one inbox, we have already had over a hundred thousand in ours. To avoid processing through the whole mailbox, the specific query has to be used.\nFor this purpose, it has to check if emails were forwarded from a selected list of mailboxes (for example, ",[846,94403,94405],{"href":94404},"mailto:hr@bravelab.io","hr@bravelab.io"," or other used aliases). To narrow results further, only messages from the last seven days that haven't been processed yet, are considered.\nFrom such a list, data from each email is used to create a new candidate with linked leads. Leads are used later for integration with Cooper CRM and Atlassian Jira.\nTo avoid making many duplicates, only the first email in the thread is allowed to create a new candidate. These criteria allow applicants to apply for multiple positions, or to reapply later.",[863,94408,94409],{"id":93926},"How does it work",[842,94411,94412],{},"When email awaiting processing is found, a sequence of things happen.\nThe message list returned from the query contains only message ids and thread ids. In Gmail API, if they are the same, it means it's the first message in a thread. Then all needed information is extracted from such a message. From the header “From” name and email address are extracted.\nFrom message content, regular expressions look for phone numbers. And from “parts” we extract attachment id’s and get all attachments. The message subject is also extracted.\nSuch information is passed to the candidate management script.",[842,94414,94415],{},"Candidates in the recruitment tab hold’s all information in a user-friendly way.",[842,94417,94418],{},"Interactive URLs allow for quick access to inbox and candidates as needed.\nNew leads create issues on Jira:",[842,94420,94421],{},"System of notification, automatic gathering, and pre-processing candidates improves speed and efficiency of the recruitment process.",[842,94423,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":94425},[94426,94427],{"id":52066,"depth":729,"text":27663},{"id":93926,"depth":729,"text":94409},"2020-03-20T00:00:00.000Z","How we integrated Gmail, Jira, Slack, and Copper CRM to automate the recruitment process, from candidate aggregation to lead tracking and task assignment.",{"src":94431},"/images/blog/musictechlab_blog_recruitment-system-integrating-gmail-bravely-jira-slack-and-copper-crm.webp",{"enabled":738,"items":94433},[94434,94436,94438],{"text":94435,"icon":52136},"Gmail API checks the inbox every 15 minutes and auto-creates candidate profiles from new emails.",{"text":94437,"icon":4845},"Leads are automatically pushed to Jira, Slack, and Copper CRM for multi-channel tracking.",{"text":94439,"icon":87068},"Only the first email in each thread creates a new candidate, preventing duplicate entries.",{},{"title":606,"description":94429},[18784,15279],"Jr4zvC-77L9L-1eeML0rI90tsIbDZmPnEjnibkE8SX0",{"id":94445,"title":594,"authors":94446,"badge":723,"body":94449,"category":756,"client":723,"date":94472,"description":94473,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":94474,"keyTakeaways":94476,"meta":94484,"navigation":738,"path":595,"seo":94485,"status":723,"stem":596,"tags":94486,"teaser":723,"__hash__":94487},"posts/blog/software-development/overdue-invoices-integration-with-the-issue-tracking-system.md",[94447],{"name":94384,"avatar":94448},{"src":94386},{"type":725,"value":94450,"toc":94470},[94451,94453,94456,94459,94462,94465,94468],[842,94452,40867],{},[842,94454,94455],{},"All companies want invoices to be paid on time. However, in reality, a significant amount of them become overdue. That creates a need for the system, that could automate the process of checking and assigning such issues to the accounting department.",[842,94457,94458],{},"Many companies utilize some kind of issue trackers and work management tools, so the idea was to integrate the existing invoice management module with issue tracker, in this case, an Atlassian Jira Software.",[842,94460,94461],{},"The task is run daily, to ensure issues are created as needed.",[842,94463,94464],{},"Invoices get processed when payment due date exceeds today, they are unpaid, and they have not been processed yet .\nAn instance of the Jira interface is created, which requires three string variables: one with URL address, another with username, and last with API token.\nThen a new Jira issue is created, which is automatically assigned to the chosen employee.\nAll other attributes are also set, such as priority, type, epics, and project where they should appear.\nThe content of the issue includes invoice number, contractor, payment date, and net amount.",[842,94466,94467],{},"Jira kanban board allows for efficient management of issues. Each of them has all the necessary information, which might be required for the assignee, to immediately proceed with the task.\nCreating an automated issue creation module improves the speed and efficiency of managing overdue invoices.",[842,94469,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":94471},[],"2020-03-19T00:00:00.000Z","Managing overdue invoices in the software development company by the process automation. Connecting and the data from wFirma with an issue tracking system.",{"src":94475},"/images/blog/musictechlab_blog_overdue-invoices-integration-with-the-issue-tracking-system.webp",{"enabled":738,"items":94477},[94478,94480,94482],{"text":94479,"icon":3271},"A daily automated task checks for overdue invoices and creates Jira issues automatically.",{"text":94481,"icon":5504},"Each Jira issue includes invoice number, contractor, payment date, and net amount.",{"text":94483,"icon":2939},"Automating overdue invoice tracking eliminates manual checking and speeds up follow-ups.",{},{"title":594,"description":94473},[18784],"mtNjowtLQBnza_QbLGeHjSLYCtHxReiHsmI-m7mGFSM",{"id":94489,"title":506,"authors":94490,"badge":723,"body":94493,"category":756,"client":723,"date":94556,"description":94557,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":94558,"keyTakeaways":94560,"meta":94568,"navigation":738,"path":507,"seo":94569,"status":723,"stem":508,"tags":94570,"teaser":723,"__hash__":94571},"posts/blog/software-development/how-to-easily-create-form-in-angular.md",[94491],{"name":90579,"avatar":94492},{"src":90581},{"type":725,"value":94494,"toc":94553},[94495,94497,94500,94503,94510,94513,94517,94520,94523,94530,94532,94538,94540,94543,94551],[842,94496,40867],{},[842,94498,94499],{},"We would like to present to you how to create a form with fields that dynamically react to user events.",[842,94501,94502],{},"To create a FormGroup, we use Form Builder, which is Bob the Builder for forms. The data defined in this way is very clear to other programmers because all the structures are visible. There is one more trick to create a form. Instead of adding a method in the component, you add it in Service with return Form Group. Why? This solution gives you a code that you can use many times. I had a situation where I represent form structure in three different components. First time when the object was created, the next when it was editing and the last one when the user changes status and application wants to show added data.",[842,94504,94505,94506,94509],{},"The code below shows you the implementation of a simple form. In an Html file, you have to add an Html form tag that will have a connection with a group using the ",[1086,94507,94508],{},"formGroup"," directive. This form contains fields that are related to a specific FormControl using the formControlName attribute.",[842,94511,94512],{},"Angular provides several simple but very often used validators such as required, email, pattern, and more. You can add Validators when you define FormControl or use method setValidators() to add when FormControl should dynamically respond to user events. Of course, you can implement your custom Validators. The code below shows you how to do this.",[863,94514,94516],{"id":94515},"how-to-implement-an-array-in-form","How to implement an array in form?",[842,94518,94519],{},"The implementation is not complicated, but it's worth seeing how to do it. In ReactiveFormsModule there is a class FormArray, which is used to aggregate FormGroup in the list. First, you should define FormGroup and next push this object into the array. In the beginning, the code in the Html file may seem a little odd because we should define a FormArray name in formArrayName, and next in the loop it’s defined formGroupName, which is determined by loop index values. The loop contains the fields of a defined group.",[842,94521,94522],{},"Angular 8 resolved a major problem with resetting FormArray. They add one ideal method reset(). In previous versions, it was recommended to iterate and delete objects or re-create FormArray.",[842,94524,94525,94526,94529],{},"When you create a good form structure, you can resolve the problem with sending values to the API server because you can use ",[895,94527,94528],{},"this.form.value"," which gives you all data defined in your FormGroup.",[842,94531,4409],{},[1013,94533,94536],{"className":94534,"code":94535,"language":1018},[1016],"title: \"Workshop\",\n\ndescription: \"Team Competency Matrix\",\n\npublishDate: Sun Mar 01 2020 00:00:00 GMT+0100,\n\nexpirationDate: Tue Mar 31 2020 00:00:00 GMT+0200,\n\nquestions: [\n\n    {\n\n        question: \"What do you think about this workshop?\"\n\n    }\n\n]\n",[895,94537,94535],{"__ignoreMap":728},[842,94539,4423],{},[842,94541,94542],{},"Reactive forms can be a tad challenging for the beginners but give much more flexibility in designing and in implementation. The solution presented in the article is to implement a FormGroup that corresponds to the business logic. The code becomes more comprehensive and correlates to the actual functionality.",[842,94544,94545,94550],{},[846,94546,94549],{"href":94547,"rel":94548},"https://github.com/AgnieszkaFierek/form-example",[850],"Here",", you can find a simple project which shows the implementation of Reactive Forms in Angular.",[842,94552,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":94554},[94555],{"id":94515,"depth":729,"text":94516},"2020-03-06T00:00:00.000Z","Develop an angular form with validation for angular js web development services. Create an angular form with multiple components easily.",{"src":94559},"/images/blog/musictechlab_blog_how-to-easily-create-form-in-angular.webp",{"enabled":738,"items":94561},[94562,94564,94566],{"text":94563,"icon":5365},"Angular Reactive Forms with FormBuilder create clear, reusable form structures.",{"text":94565,"icon":1769},"Extracting FormGroup creation into a Service lets you reuse the same form across components.",{"text":94567,"icon":13562},"FormArray handles dynamic lists of fields, and Angular 8 added a clean reset() method.",{},{"title":506,"description":94557},[18784],"ViwrfVhQdkC6McRN3aPR1Dz2HjS-gybOyz1lX9IhOnY",{"id":94573,"title":398,"authors":94574,"badge":723,"body":94577,"category":756,"client":723,"date":94737,"description":94738,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":94739,"keyTakeaways":94741,"meta":94749,"navigation":738,"path":399,"seo":94750,"status":723,"stem":400,"tags":94751,"teaser":723,"__hash__":94752},"posts/blog/software-development/change-detection-mechanism-in-angular.md",[94575],{"name":87508,"avatar":94576},{"src":87797},{"type":725,"value":94578,"toc":94733},[94579,94581,94584,94587,94601,94609,94615,94617,94626,94629,94646,94650,94658,94665,94669,94674,94676,94686,94690,94693,94700,94709,94712,94715,94728,94731],[842,94580,40867],{},[842,94582,94583],{},"In Angular 1.x, an ancestor of the newest Angular, there was a $digest cycle. It was a tool for listening to the component’s changes. Unfortunately, it caused a lot of issues with the optimization of applications. In the new Angular, creators replaced it with Change Detection.",[842,94585,94586],{},"Change Detection is a build-in mechanism that is responsible for checking bindings in the component’s template. It turns on in several cases, after:",[958,94588,94589,94592,94595],{},[961,94590,94591],{},"API calls",[961,94593,94594],{},"DOM events, e.g. after clicking on the button, submitting the form",[961,94596,94597,94598],{},"usage of ",[964,94599,94600],{},"setTimeout(), setInterval()",[842,94602,94603,94604,94608],{},"@Component({\nselector: 'counter',\ntemplate: '\u003Cspan (click)=”incrementCounter()”>",[94605,94606],"binding",{"value":94607},"counter","'\n})\nexport class CounterComponent {\ncounter = 0;",[1013,94610,94613],{"className":94611,"code":94612,"language":1018},[1016],"incrementCounter() {\n    this.counter += 1;\n}\n",[895,94614,94612],{"__ignoreMap":728},[842,94616,4423],{},[842,94618,94619,94620,94622,94623,94625],{},"In the code above, there is a simple binding of the ",[964,94621,94607],{}," variable. After clicking on the ",[964,94624,1086],{}," element, the Change Detector will be turned on and will start the notification process.",[842,94627,94628],{},"Each component has its Change Detector instance that is created automatically in the application runtime phase. When some Change Detector meets the change in bindings, it turns on all Change Detectors for the whole components tree. As you may know, components in Angular have a tree structure. It means that when you are implementing components in Angular, you put them into each other, and you create a tree of components with AppComponent as a root.",[958,94630,94631,94634,94637,94640,94643],{},[961,94632,94633],{},"Change Detector in UserCounterComponent detects change",[961,94635,94636],{},"Change Detector related to UserCounterComponents uses ngZone for notifying AppComponent Change Detector that it detected change",[961,94638,94639],{},"AppComponent Change Detector receives information from UserCounterComponent Change Detector. It doesn’t know what changed in UserCounterComponent, but it has to notify other Change Detectors in the branch",[961,94641,94642],{},"AppComponent Change Detector notifies other components Change Detectors from the up to the down: AppComponent CD -> UserProfile CD -> UserVisits CD - UserCounter CD",[961,94644,94645],{},"Notified Change Detectors force re-rendering",[863,94647,94649],{"id":94648},"change-detection-onpush-strategy","Change Detection OnPush strategy",[842,94651,94652,94653,94657],{},"@Component({\nselector: 'counter',\ntemplate: '",[1086,94654,94655],{},[94605,94656],{"value":94607},"',\nchangeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class CounterComponent {\n@Input() counter = 0;",[842,94659,94660,94661,94664],{},"In @Component decorator, we can change the Change Detection strategy. The default value is automatically set to ",[964,94662,94663],{},"ChangeDetectionStrategy.default."," We can change it to the OnPush strategy. OnPush strategy allows us to optimize our app a bit. It informs Change Detector that our component depends only on its @Input() and Change Detection mechanism for this component will be turned on only when the value in @Input() changes or when we force detection manually.",[842,94666,94603,94667,94657],{},[94605,94668],{"value":94607},[1013,94670,94672],{"className":94671,"code":94612,"language":1018},[1016],[895,94673,94612],{"__ignoreMap":728},[842,94675,4423],{},[842,94677,94678,94679,94681,94682,94685],{},"It means that after clicking on ",[964,94680,1086],{}," element, the Change Detector will not be turned on. The component that depends only on @Input() will be re-render when the @Input() changes, so other events from this component will not notify Change Detector from AppComponent and the application will be re-rendered less and in cases when it will be necessary. Change Detection in component with OnPush strategy will be also turned on when some ",[964,94683,94684],{},"Observable"," in this component will get new data from the stream.",[863,94687,94689],{"id":94688},"expressionchangedafterithasbeencheckederror","ExpressionChangedAfterItHasBeenCheckedError",[842,94691,94692],{},"While working with Angular, you probably had this error, not only once. What does it mean?",[842,94694,94695,94696,94699],{},"@Component({\nselector: 'timer',\ntemplate: ",[895,94697,94698],{},"        \u003Cspan [textContent]=\"time | date:'hh:mm:ss:SSS'\">\u003C/span>         \u003Cbutton (click)=\"0\">Trigger Change Detection\u003C/button>    ","\n})\nexport class TimerComponent {\nget time() {\nreturn Date.now();\n}\n}",[842,94701,94702,94703,94705,94706,94708],{},"Let’s take a look at the example above. We have a ",[964,94704,1086],{}," element that displays the current time formatted by ",[964,94707,9323],{}," pipe and a simple button with an empty callback function. When the user clicks on the button, the Change Detection mechanism turns on and checks bindings. It notifies the AppComponent Change Detector, and after the whole notification flow, the TimerComponent is re-rendered. Everything is working properly, but in the console, we have the following error:",[842,94710,94711],{},"Well, after each change detection cycle, Angular runs the detection cycle one more time to ensure that all bindings have the same value as in the previous cycle. The check cycle is launched just after the end of the previous cycle. It detects differences and throws the error.\nAlthough, why does Angular need this check? Well, imagine that some properties of components have been updated during the change detection cycle. As a result, expressions produce new values that are inconsistent with the previous ones. Angular could run another change detection cycle to synchronize the application state with the user interface. What if, during that process, some properties are updated again? Angular could fall in an infinite loop of change detection runs. That happened quite often in Angular 1.x.",[842,94713,94714],{},"ExpressionChangedAfterItHasBeenCheckedError is thrown to avoid this situation Angular detection cycle.",[842,94716,94717,94718,94721,94722,27714,94724,94727],{},"You can work around issues related to ExpressionChangedAfterItHasBeenCheckedError by injecting ",[964,94719,94720],{},"ChangeDetectorRef"," in your component and run method detectChanges from ",[964,94723,94720],{},[964,94725,94726],{},"ngAfterViewChecked"," life cycle hook. It will force the detection cycle once again and update bindings.",[842,94729,94730],{},"Angular is a huge and powerful framework. Knowledge about one of the most important mechanisms in Angular could be useful in current work. Information about Change Detection may also be useful with avoiding ExpressionChangedAfterItHasBeenCheckedError.",[842,94732,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":94734},[94735,94736],{"id":94648,"depth":729,"text":94649},{"id":94688,"depth":729,"text":94689},"2020-03-05T00:00:00.000Z","Change Detection mechanism in Angular js web development services. How to handle with ExpressionChangedAfterItHasBeenCheckedError",{"src":94740},"/images/blog/musictechlab_blog_change-detection-mechanism-in-angular.webp",{"enabled":738,"items":94742},[94743,94745,94747],{"text":94744,"icon":5365},"Angular Change Detection triggers after API calls, DOM events, and setTimeout/setInterval.",{"text":94746,"icon":1779},"One change in any component notifies all Change Detectors in the entire tree.",{"text":94748,"icon":2939},"OnPush strategy limits detection to @Input() changes, improving app performance.",{},{"title":398,"description":94738},[18784],"-mjqCLrEth4AfCaMRKmjnpiCmTIl5sYk3nW363Ifjhc",{"id":94754,"title":538,"authors":94755,"badge":723,"body":94758,"category":756,"client":723,"date":94874,"description":94875,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":94876,"keyTakeaways":94878,"meta":94886,"navigation":738,"path":539,"seo":94887,"status":723,"stem":540,"tags":94888,"teaser":723,"__hash__":94889},"posts/blog/software-development/installing-proxmox-on-dedicated-server-from-ovh.md",[94756],{"name":90132,"avatar":94757},{"src":94277},{"type":725,"value":94759,"toc":94869},[94760,94762,94765,94769,94772,94775,94778,94782,94791,94811,94815,94818,94821,94824,94827,94830,94834,94840,94843,94846,94852,94864,94867],[842,94761,40867],{},[842,94763,94764],{},"Proxmox is a useful set of virtualization tools that enable, e.g., creating and restoring backups, monitoring resources like CPU and RAM usage, storage capacity and throughput, ingoing and outgoing network traffic. In this article, you can discover how to install it on a dedicated server from OVH.",[863,94766,94768],{"id":94767},"what-is-proxmox","What is Proxmox?",[842,94770,94771],{},"According to its website, Proxmox Virtual Environment is a complete open-source platform for all-inclusive enterprise virtualization that tightly integrates KVM hypervisor, LXC containers, software-defined storage and networking functionality on a single platform. It allows to easily manage high availability clusters and disaster recovery tools with the built-in web interface.",[842,94773,94774],{},"Our use case is running a couple of legacy LXC containers with relatively large storage. Migrating them to a cloud solution would not bring any significant advantages or cost reductions, and the process would not be trivial. Apart from that, Proxmox is a very powerful and easy to use set of tools. Creating and restoring backups is a one-click action. Monitoring resources like CPU and RAM usage, storage capacity and throughput, ingoing and outgoing network traffic, is set up out-of-the-box. Furthermore, it can be effortlessly extended with an alerting system.",[842,94776,94777],{},"**Disclaimer: **As we didn’t need high availability in our project, we used only one server and didn’t set up any extra HA features that Proxmox offers.",[863,94779,94781],{"id":94780},"what-was-easy","What was easy?",[842,94783,94784,94785,94790],{},"Buying a dedicated server from OVH is quick and easy. Go ",[846,94786,94789],{"href":94787,"rel":94788},"https://www.ovh.pl/serwery_dedykowane/",[850],"to the OVH website",", pick the configuration that suits your needs, your favourite Linux distribution, pay, and by the next day you should be a proud owner of a fresh server. Don’t forget about ordering extra IP addresses if you are planning to run VMs or VM-like containers.\nA non-technical thing worth mentioning is that we recommend adding company credit card as a payment method for bought services. This will prevent OVH from shutting down your machines when the accounting department forgets to pay the invoice before the billing period ends.",[842,94792,94793,94794,94799,94800,94805,94806,94810],{},"One you can access the machine, set up your sshd service - we recommend disabling password login and installing fail2ban. It will help with keeping your server secure, by blocking IP addresses that attempt brute-force attacks against your sshd service. Next, create a very strong password for the root user - it will be required later, to login to the Proxmox web panel.\nNow comes the time to install Proxmox VE itself. If you have chosen Debian as your OS, you can use ",[846,94795,94798],{"href":94796,"rel":94797},"https://pve.proxmox.com/wiki/Install_Proxmox_VE_on_Debian_Stretch",[850],"the official instructions"," or unofficial, but more detailed ",[846,94801,94804],{"href":94802,"rel":94803},"https://www.howtoforge.com/tutorial/how-to-install-proxmox-ve-4-on-debian-8-jessie/",[850],"guide",". Once you finish the installation and it is possible to login to the web panel (",[846,94807,94808],{"href":94808,"rel":94809},"https://your-server-ip:8006/",[850],"), everything should work fine, apart from networking, which brings us to the next chapter.",[863,94812,94814],{"id":94813},"what-wasnt-easy","What wasn’t easy?",[842,94816,94817],{},"Although most instructions for Proxmox installation are clear, the parts about setting up the networking are often too simple to make sense or too complex to comprehend. We won’t go into much detail and only explain what worked for our setup on OVH.\nEdit the /etc/network/interfaces file:",[842,94819,94820],{},"auto lo",[842,94822,94823],{},"iface lo inet loopback",[842,94825,94826],{},"source /etc/network/interfaces.d/*",[842,94828,94829],{},"auto eno1",[5539,94831,94833],{"id":94832},"real-ip-address","real IP address",[1013,94835,94838],{"className":94836,"code":94837,"language":1018},[1016],"iface eno1 inet manual\n\niface eno1 inet static\n\n    address  your-server-ip\n\n    netmask  255.255.255.0\n\n    gateway  your-server-ip-but-wtih-254-ending\n",[895,94839,94837],{"__ignoreMap":728},[842,94841,94842],{},"auto vmbr0",[5539,94844,32405],{"id":94845},"bridge",[1013,94847,94850],{"className":94848,"code":94849,"language":1018},[1016],"iface vmbr0 inet static\n\naddress your-server-ip\n\nnetmask 255.255.255.255\n\nbridge_ports eno1\n\nbridge_stp off\n\nbridge_fd 0\n\npost-up route add your-server-ip-but-with-254-ending dev vmbr0\n\npost-up route add default gw your-server-ip-but-with-254-ending\n",[895,94851,94849],{"__ignoreMap":728},[842,94853,94854,94855,94858,94859,94863],{},"our-server-ip is the one displayed as public in the “Network interfaces” tab of your OVH dedicated server dashboard, e.g. 12.34.56.78. For this example, the your-server-ip-but-with-254-ending is 12.34.56.254 - that’s how OVH creates gateway addresses for its IPs.\nOnce you apply this configuration (",[895,94856,94857],{},"sudo systemctl restart networking.service","), you should be able to set up virtual machines and containers with their public IP addresses. To get an IP address that can be used, you need to buy a failover IP for your server and attach a virtual MAC address (",[846,94860,94804],{"href":94861,"rel":94862},"https://docs.ovh.com/gb/en/dedicated/network-bridging/",[850],"). Then, when setting up networking for a VM/container via the Proxmox panel, enter your MAC, IP, and gateway addresses. Example for 33.33.33.33 IP with 33:33:33:33:33:33 MAC:",[842,94865,94866],{},"It’s been about 2 months since we started using Proxmox, and we haven’t had any problems with it. Containers are running without any issues, and their performance is good and stable.",[842,94868,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":94870},[94871,94872,94873],{"id":94767,"depth":729,"text":94768},{"id":94780,"depth":729,"text":94781},{"id":94813,"depth":729,"text":94814},"2020-02-13T00:00:00.000Z","A step-by-step guide to installing Proxmox on a dedicated OVH server, including network configuration, LXC containers, and backup management tips.",{"src":94877},"/images/blog/musictechlab_blog_installing-proxmox-on-dedicated-server-from-ovh.webp",{"enabled":738,"items":94879},[94880,94882,94884],{"text":94881,"icon":13562},"Proxmox provides one-click backups, resource monitoring, and LXC container management out of the box.",{"text":94883,"icon":3847},"OVH network configuration for bridge interfaces and failover IPs is the trickiest installation step.",{"text":94885,"icon":7495},"Disable password login and install fail2ban immediately after setting up the dedicated server.",{},{"title":538,"description":94875},[15279],"BROtbacucONPRPX8C-5G4b7ztaNDlXCB2mei6FCB4LI",{"id":94891,"title":614,"authors":94892,"badge":723,"body":94896,"category":756,"client":723,"date":94931,"description":94932,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":94933,"keyTakeaways":94935,"meta":94943,"navigation":738,"path":615,"seo":94944,"status":723,"stem":616,"tags":94945,"teaser":723,"__hash__":94946},"posts/blog/software-development/scratch-me-integration-with-the-copper-crm.md",[94893],{"name":89504,"avatar":94894},{"src":94895},"/images/people/pawel-kotoniak.webp",{"type":725,"value":94897,"toc":94928},[94898,94900,94908,94912,94915,94918,94925],[842,94899,40867],{},[842,94901,94902,94903,94907],{},"The previous ",[846,94904,94906],{"href":94905},"/blog/scratch-me-a-simple-chrome-extension-which-will-increase-your-productivity","article"," described the background of the idea for creating the Scratch Me extension. The first version of the plugin was a prototype working on the principle of adding a button to each Facebook post and showing the extracted data. In the second stage of developing the plugin, we focused on its integration with the Copper CRM system. In the following article, you can read how to configure and use the Scratch Me extension.",[863,94909,94911],{"id":94910},"how-does-the-extension-work","How does the extension work?",[842,94913,94914],{},"As you know, the extension adds a „Scratch Me” button to every Facebook post in a group. The new version of the plugin displays a window with the form, where you have three options: send the extracted data to Copper CRM, display it as JSON or cURL.",[842,94916,94917],{},"Scratch Me allows you to edit the data before sending it to the system. When all data is correct, you can click on the \"Send to Copper\" button and the data will be sent to the system.",[842,94919,94920,94921],{},"After setting up the connection, you can use the extension in just 3-4 clicks. All data is completed as automatically as possible and prepared to be sent. Integration with other CRM systems is planned in the future, we are open to your proposal. If you are interested in this kind of project, contact us.",[846,94922],{"href":94923,"rel":94924},"https://chrome.google.com/webstore/detail/scratch-me/odkmhaknpdkfhgpeiacgojmmfiggepfd",[850],[842,94926,94927],{},"The previous article described the background of the idea for creating the Scratch Me extension. The first version of the plugin was a prototype working on the principle of adding a button to each Facebook post and showing the extracted data. In the second stage of developing the plugin, we focused on its integration with the Copper CRM system. In the following article, you can read how to configure and use the Scratch Me extension.",{"title":728,"searchDepth":729,"depth":729,"links":94929},[94930],{"id":94910,"depth":729,"text":94911},"2020-02-06T00:00:00.000Z","A chrome plugin that increases productivity in a lead generation process by extracting the data from the post published on the Facebook social media platform",{"src":94934},"/images/blog/musictechlab_blog_scratch-me-integration-with-the-copper-crm.webp",{"enabled":738,"items":94936},[94937,94939,94941],{"text":94938,"icon":2939},"After initial setup, sending a Facebook lead to Copper CRM takes just 3-4 clicks.",{"text":94940,"icon":87068},"The plugin auto-detects if a post author already exists in Copper to prevent duplicates.",{"text":94942,"icon":13562},"Connection settings persist between sessions, so users configure Copper credentials only once.",{},{"title":614,"description":94932},[18784],"8m9VOuDPk0NefywNNpv4MyxIADhG5OZFGzQeM1U17GI",{"id":94948,"title":354,"authors":94949,"badge":723,"body":94952,"category":756,"client":723,"date":95164,"description":95165,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":95166,"keyTakeaways":95168,"meta":95178,"navigation":738,"path":355,"seo":95179,"status":723,"stem":356,"tags":95180,"teaser":723,"__hash__":95181},"posts/blog/software-development/brave-3-0-part-4-technologies-behind-and-final-series-recap.md",[94950],{"name":834,"to":720,"avatar":94951},{"src":722},{"type":725,"value":94953,"toc":95140},[94954,94956,94959,94963,94966,94989,94992,94995,94998,95001,95005,95008,95012,95014,95017,95019,95021,95024,95028,95031,95035,95038,95042,95046,95050,95054,95065,95069,95087,95091,95094,95097,95108,95110,95112,95117,95121,95124,95126,95129,95138],[842,94955,40867],{},[842,94957,94958],{},"We are a Python & JavaScript software development company with experience in building dedicated apps based on Django. We wanted to have a customizable website that we can improve day by day and where marketing people can edit content in a convenient way.",[863,94960,94962],{"id":94961},"there-are-tons-of-available-content-management-systems","There are tons of available Content Management Systems...",[842,94964,94965],{},"Why have we decided to use Django-CMS?",[958,94967,94968,94971,94974,94977,94980,94983,94986],{},[961,94969,94970],{},"National Geographic",[961,94972,94973],{},"NASA",[961,94975,94976],{},"Parrot",[961,94978,94979],{},"PBS",[961,94981,94982],{},"AAMC",[961,94984,94985],{},"Salt.",[961,94987,94988],{},"L'Oréal Paris",[842,94990,94991],{},"For now, it is a mature piece of code written in Python. Django-CMS has over a dozen built-in plugins. We can easily build our own, and that is another factor why we decided to use this software.",[842,94993,94994],{},"We need to know that there is no perfect CMS. Sometimes WordPress will be enough, but as far as we are Django enthusiasts, it was easier to use our favorite language and framework.",[842,94996,94997],{},"Why monolithic?",[842,94999,95000],{},"If you forget about SEO before programming, you will have a lot of problems with hacking your software to work correctly for generic SEO, e.g. you will have to use the Server Side Rendering techniques. Our previous website was built with Angular2. From the SEO point of view, it was a big mistake. Owing to the fact that we have lost one year of generic SEO (sic!), Angular wasn’t the best choice for that kind of website.",[863,95002,95004],{"id":95003},"technologies-and-tools-around-the-website","Technologies and tools around the website",[842,95006,95007],{},"Building the website is not only about marketing communication, mockups, wireframes, designing, and coding. There are tons of libraries and tools which enable us to build a highly performed website. If you don’t know them yet, you can check them now:",[1074,95009,95011],{"id":95010},"dockerhow-many-times-have-you-been-asked-about-generic-seo-whenever-you-hear-it-ask-yourself-either-you-have-monolithic-or-modern-frontend-app-built-in-with-reactjsangularvuejs","DockerHow many times have you been asked about generic SEO? Whenever you hear it - ask yourself either you have monolithic or “modern” frontend app built-in with React.js/Angular/Vue.js?",[842,95013,95000],{},[863,95015,95004],{"id":95016},"technologies-and-tools-around-the-website-1",[842,95018,95007],{},[1074,95020,65407],{"id":34369},[1074,95022,65816],{"id":95023},"sentry",[1074,95025,95027],{"id":95026},"newrelic","Newrelic",[842,95029,95030],{},"If yes, you need to check the powerful Newrelic platform. In the free plan, you can easily monitor common problems on different layers, such as databases, external services, etc.. In a paid plan, everything around performance problems is very easy to find and analyze.",[1074,95032,95034],{"id":95033},"cloudflare","Cloudflare",[842,95036,95037],{},"However, how can we check what is wrong when the users left our page just after they opened it? The answer is a Hotjar platform. It is a useful tool to analyze users’ behavior and catch the weak points of the website.",[1074,95039,95041],{"id":95040},"iubenda","Iubenda",[1074,95043,95045],{"id":95044},"facebook-tracking-pixel","Facebook Tracking Pixel",[863,95047,95049],{"id":95048},"a-few-words-about-seo","A few words about SEO",[1074,95051,95053],{"id":95052},"what-does-performance-look-like","What does performance look like?",[958,95055,95056,95059,95062],{},[961,95057,95058],{},"Redis cache",[961,95060,95061],{},"Cloudflare cache",[961,95063,95064],{},"Optimized templates (e.g with a statement in Django)",[863,95066,95068],{"id":95067},"pwa-web-progressive-apps","PWA - Web Progressive Apps",[842,95070,95071,95072,95076,95077,95081,95082,861],{},"During the development process, we decided to adjust the setup in order to use our website as a PWA application. Hopefully, we have found the Django app, which could handle everything that we needed to start. The Django app allows us to set up every PWA option via Django settings. ",[846,95073,94549],{"href":95074,"rel":95075},"https://github.com/silviolleite/django-pwa",[850],", you can find out more information.\nSome time ago, ",[846,95078,95080],{"href":95079},"/blog/progressive-web-apps-a-new-way-of-creating-mobile-application","we wrote an article about PWA",", where you can read about the advantages and disadvantages of the approach of doing mobile apps. If you want to see more examples of using PWA in action, please visit our partner’s site: ",[846,95083,95086],{"href":95084,"rel":95085},"https://onilab.com/blog/20-progressive-web-apps-examples/%C2%A0",[850],"https://onilab.com/blog/20-progressive-web-apps-examples/",[863,95088,95090],{"id":95089},"pros-cons-of-the-tech-stack","Pros & Cons of the tech stack",[842,95092,95093],{},"The transformation of the website enabled us to meet our marketing and performance assumptions. Of course there is always room for improvement. We are aware that we use a niche CMS and that for typical websites, Python is less popular language than PHP, so from my perspective:",[1074,95095,13686],{"id":95096},"pros",[958,95098,95099,95102,95105],{},[961,95100,95101],{},"Modern and customizable CMS",[961,95103,95104],{},"Complete website - not only a simple page but with full of marketing tools",[961,95106,95107],{},"We can improve things really fast",[1074,95109],{"id":728},[842,95111,13691],{},[958,95113,95114],{},[961,95115,95116],{},"Niche CMS with fewer plugins than WordPress",[863,95118,95120],{"id":95119},"plan-for-the-future-development","Plan for the future development",[842,95122,95123],{},"For now, we are going to develop and polish some unfinished modules, and then we will start to improve the content quality.",[863,95125,25699],{"id":8196},[842,95127,95128],{},"We decided to build the website ourselves. However it was not easy to make this decision. There is a belief that Software Houses cannot build their own website because of a lack of agreement during the designing process. Yes, it was difficult to ensure that everyone, who was involved in the project, is on the same page. Moreover, there are always commercial projects to do.Therefore, the internal projects are often put aside. One of the biggest challenges is to always set objectives and priorities. As a manager, you need to be sure that you will find time to manage the in-house project.\nFinally, we haven’t decided about the project’s deadline. We assumed that the project should be improved continuously. We finished core development and now we are focused on the content and quality improvements.",[842,95130,95131,95132,95137],{},"You need to remember that tools around the bravelab.io are very helpful in building custom web applications too.\nIf you want to improve your app please,",[846,95133,95136],{"href":95134,"rel":95135},"https://bravelab.io/#contact-us",[850],"let us know",", we will help you.",[842,95139,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":95141},[95142,95143,95146,95154,95157,95158,95162,95163],{"id":94961,"depth":729,"text":94962},{"id":95003,"depth":729,"text":95004,"children":95144},[95145],{"id":95010,"depth":1112,"text":95011},{"id":95016,"depth":729,"text":95004,"children":95147},[95148,95149,95150,95151,95152,95153],{"id":34369,"depth":1112,"text":65407},{"id":95023,"depth":1112,"text":65816},{"id":95026,"depth":1112,"text":95027},{"id":95033,"depth":1112,"text":95034},{"id":95040,"depth":1112,"text":95041},{"id":95044,"depth":1112,"text":95045},{"id":95048,"depth":729,"text":95049,"children":95155},[95156],{"id":95052,"depth":1112,"text":95053},{"id":95067,"depth":729,"text":95068},{"id":95089,"depth":729,"text":95090,"children":95159},[95160,95161],{"id":95096,"depth":1112,"text":13686},{"id":728,"depth":1112,"text":728},{"id":95119,"depth":729,"text":95120},{"id":8196,"depth":729,"text":25699},"2020-01-31T00:00:00.000Z","Technologies used to develop a commercial website of the custom software development company. Those technologies improve website performance and effectiveness.",{"src":95167},"/images/blog/musictechlab_blog_brave-3-0-part-4-technologies-behind-and-final-series-recap.webp",{"enabled":738,"items":95169},[95170,95172,95174,95176],{"text":95171,"icon":5365},"Django CMS was chosen over WordPress for customizability using Python.",{"text":95173,"icon":3847},"An Angular frontend caused one full year of lost organic SEO performance.",{"text":95175,"icon":13562},"Cloudflare, Sentry, NewRelic, and Hotjar formed the monitoring and performance stack.",{"text":95177,"icon":12412},"Docker let developers set up and start local development within 5 minutes.",{},{"title":354,"description":95165},[18784,15279],"je3aHDFoG2x3pOnsf1GedH5fCSs5l9mtqVn_wBQyG54",{"id":95183,"title":306,"authors":95184,"badge":95187,"body":95188,"category":756,"client":723,"date":95369,"description":95370,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":95371,"keyTakeaways":95373,"meta":95383,"navigation":738,"path":307,"seo":95384,"status":723,"stem":308,"tags":95385,"teaser":723,"__hash__":95386},"posts/blog/software-development/10-benefits-of-outsourcing-software-development-services.md",[95185],{"name":93723,"avatar":95186},{"src":93725},{"label":93516,"color":32383},{"type":725,"value":95189,"toc":95364},[95190,95192,95195,95204,95208,95217,95220,95225,95228,95233,95236,95241,95244,95249,95252,95257,95260,95265,95268,95273,95276,95281,95284,95289,95292,95296,95299,95304,95307,95312,95315,95320,95323,95328,95331,95334,95338,95341,95347,95353,95359,95362],[842,95191,40867],{},[842,95193,95194],{},"There are different possibilities to outsource the work - you can choose nearshore, offshore or onshore. The difference between them depends on the destination where you decide to redistribute your work. Onshore refers to a third party, which is located in the same country as your company, nearshore defines outsourcing to the company located in the same geographical region, e.g. your neighbor country, offshore means outsourcing in another country.",[842,95196,95197,95198,95203],{},"Based on ",[846,95199,95202],{"href":95200,"rel":95201},"https://vironit.com/15-benefits-of-outsourcing-software-development/",[850],"VironIT's"," article, we would like to present the pros and cons of outsourcing the software development process.",[863,95205,95207],{"id":95206},"advantages","Advantages",[842,95209,95210,95214],{},[1027,95211],{"alt":95212,"src":95213},"Benefits of outsourcing software development","/images/blog/musictechlab_blog_outsourcing-benefits.webp",[996,95215,95216],{},"1. Cost Savings",[842,95218,95219],{},"Outsourcing the tasks or projects enables you to save money related to hiring, onboarding, training, and housing employees in your company. What is more, you would not need to equip your workers with supplies that are indispensable to provide software development services.\nFurthermore, the cost of software development services diversifies between countries. In Eastern Europe, the cost of the same performed labor may differ by up to 80% compared to West Europe or North America.",[842,95221,95222],{},[996,95223,95224],{},"2. Time-saving",[842,95226,95227],{},"Software development takes less time when you outsource some of the tasks to another company. Collaborating with an external company enables you to conduct the project much faster and efficiently. In addition, the time which you spend on finding an appropriate IT company to outsource your work is shorter than the recruitment process.",[842,95229,95230],{},[996,95231,95232],{},"3. External experts",[842,95234,95235],{},"When you have a lack of internal experience and knowledge, the external experts will be needed to deliver the project on time. Not only will you meet deadlines, but also you exchange knowledge with the outsourcing company and learn one from another.",[842,95237,95238],{},[996,95239,95240],{},"4. Focused strategy",[842,95242,95243],{},"Your internal team might be too small to conduct several projects at the same time. Dividing team efforts on different projects and tasks might result in low-quality performance as well as delays in delivering the product. Therefore, you should outsource some of the work to focus on the main tasks which enable your in-house team to provide high-quality services.",[842,95245,95246],{},[996,95247,95248],{},"5. Flexibility",[842,95250,95251],{},"Hiring new employees for short-term projects is inefficient. It is important to take into consideration the downtime, which generates costs related to the maintenance of the additional employee. Nevertheless, you can outsource the particular project and avoid the costs linked with hiring, training, and maintaining new employees.",[842,95253,95254],{},[996,95255,95256],{},"6. Reduced time to market",[842,95258,95259],{},"The IT business is developing very quickly, and it is essential to innovate quickly in order to gain a competitive advantage in the market. Outsourcing is the way to speed up the processes of software development in your company and being first in the market, which might occur as a success factor for your organization. Hiring an external team of experts enables you to deliver the product quicker and with higher quality.",[842,95261,95262],{},[996,95263,95264],{},"7. Technological Advances",[842,95266,95267],{},"Not only may a lack of experts be the reason to outsource the work, but also inefficient technology. Finding external service providers with advanced technology is crucial for developing your business and gaining a competitive advantage in the market. Outsourcing the work to another company that has a proven track record of excellence enables you to develop cutting-edge solutions.",[842,95269,95270],{},[996,95271,95272],{},"8. Reaching a Broader Market",[842,95274,95275],{},"Outsourcing is a chance for your business to grow in many directions, which would not be possible with your capacities. Working with highly-qualified developers enables you to improve your performance and reach a broader audience around the globe.",[842,95277,95278],{},[996,95279,95280],{},"9. Bringing in a fresh perspective",[842,95282,95283],{},"The routine during daily-work might minimize creating thought-provoking solutions and out-of-the-box ideas. Working with external partners provides your company with new knowledge and meaningful feedback. As a result, you improve your work as well as the project.",[842,95285,95286],{},[996,95287,95288],{},"10. Lower Risk",[842,95290,95291],{},"Collaborating with an external company that has long experience and a proven set of skills, you reduce the risk associated with not delivering a high-quality project on time.",[863,95293,95295],{"id":95294},"disadvantages","Disadvantages",[842,95297,95298],{},"Every company that decides to outsource the project has doubts regarding the aspects mainly related to trust.",[842,95300,95301],{},[996,95302,95303],{},"Lack of Control",[842,95305,95306],{},"The main problem in establishing trust is a lack of control over the process in an outsourcing company. What is more, an external company usually has different values, mission, business goals, and management system, which also may affect your concerns about project control.",[842,95308,95309],{},[996,95310,95311],{},"Hidden cost",[842,95313,95314],{},"Collaborating with the external team might generate additional costs for providing the services which were not included in the contract. When you decide on outsourcing, you have to prepare for unexpected expenses and related negotiations.",[842,95316,95317],{},[996,95318,95319],{},"Risk of exposing confidential data",[842,95321,95322],{},"As already mentioned, every relationship is built on trust. It might be hard to entrust an external partner with information about your clients. Therefore, it is essential to formulate a contract that will protect your company and clients' data.",[842,95324,95325],{},[996,95326,95327],{},"The time zone differences",[842,95329,95330],{},"When an external company is located in another time zone, it seems to be problematic in terms of communication and collaboration. However, different time zones allow you to work almost continuously. When the outsourcing company starts working-day, they already have the answers to the questions they asked at the end of the previous day. This is because the offshore company works during the client’s night time, so the client has the results the next morning.",[842,95332,95333],{},"Certainly, there might be some disagreement over standards, management systems, and performance. However, when you focus on the field in which you perform the best and outsource the rest, your business definitely will benefit from that solution.",[863,95335,95337],{"id":95336},"implementation-of-the-outsourcing-strategy","Implementation of the outsourcing strategy",[842,95339,95340],{},"In order to choose the right software development company for outsourcing, you should follow three simple steps:",[842,95342,95343,95346],{},[996,95344,95345],{},"1. Define your goals","\nYou should describe in detail the process, service or product that you want to outsource to define the technology, skill set, and profile of the outsourcing company.",[842,95348,95349,95352],{},[996,95350,95351],{},"2. Check experience, references, and portfolio","\nIt is important to verify the company's experience and its ability to deliver the project. The portfolio is very useful to distinguish previous work and the technology, which is used by the company.",[842,95354,95355,95358],{},[996,95356,95357],{},"3. Consider cross-cultural features","\nLast but not least, it is essential to consider the country and company culture to pick the right service provider and establish an effective relationship.",[842,95360,95361],{},"Nevertheless, there are reasons why companies decide to outsource software development processes. The main are low-cost, collaborating with professionals, and delivering a high-quality product on time. Large companies choose that strategy, which occurs to be successful to establish the highly-valued business.",[842,95363,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":95365},[95366,95367,95368],{"id":95206,"depth":729,"text":95207},{"id":95294,"depth":729,"text":95295},{"id":95336,"depth":729,"text":95337},"2020-01-13T00:00:00.000Z","The main advantages of outsourcing software development. Offshore software development outsourcing challenges and strategy implementation.",{"src":95372},"/images/blog/musictechlab_blog_10-benefits-of-outsourcing-software-development-services.webp",{"enabled":738,"items":95374},[95375,95377,95379,95381],{"text":95376,"icon":5504},"Eastern Europe outsourcing can cut software development costs by up to 80%.",{"text":95378,"icon":2939},"Outsourcing accelerates time to market by tapping external expert teams.",{"text":95380,"icon":3847},"Hidden costs and loss of control are the top risks to plan for.",{"text":95382,"icon":3271},"Short-term projects benefit most from outsourcing vs. hiring full-time staff.",{},{"title":306,"description":95370},[74615],"5NzXBU1DiV-ol-TOU5KhWR-C6mG8XcogfE-986T0_MI",{"id":95388,"title":650,"authors":95389,"badge":723,"body":95392,"category":756,"client":723,"date":95616,"description":95617,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":95618,"keyTakeaways":95620,"meta":95628,"navigation":738,"path":651,"seo":95629,"status":723,"stem":652,"tags":95630,"teaser":723,"__hash__":95631},"posts/blog/software-development/top-10-articles-through-the-eyes-of-our-developers.md",[95390],{"name":93723,"avatar":95391},{"src":93725},{"type":725,"value":95393,"toc":95604},[95394,95396,95399,95403,95406,95419,95423,95426,95439,95443,95446,95459,95463,95466,95479,95483,95486,95499,95503,95506,95519,95523,95526,95539,95543,95546,95559,95563,95566,95579,95583,95586,95599,95602],[842,95395,40867],{},[842,95397,95398],{},"It is highly important to keep up to date with thought-provoking solutions, updates, and tricks to deliver high-quality products and services. The year 2019 is over, therefore we would like to present the top 10 articles that we found very interesting and helpful during our daily work.",[863,95400,95402],{"id":95401},"angular-debugging-expression-has-changed-after-it-was-checked-simple-explanation-and-fix","Angular Debugging \"Expression has changed after it was checked\": Simple Explanation (and Fix)",[842,95404,95405],{},"The article covers the topic of an error message: \"Expression has changed after it was checked\", which occurs while building Angular application. To completely understand the error message and debugging techniques, the author explains why the error occurs in the first place and how to identify it in the Angular development mode.\nGoing through the article, it is noticeable that the described error enables us to recognize the mistakes in the early stage of the development process. Therefore, developers can avoid certain issues with unrelated components or using the previous version of the data, while building UIs.",[842,95407,95408],{},[964,95409,95410,95411,861],{},"See the source from the Angular University ",[964,95412,95413],{},[846,95414,95417],{"href":95415,"rel":95416},"https://blog.angular-university.io/angular-debugging/",[850],[964,95418,91247],{},[863,95420,95422],{"id":95421},"the-worst-api-ever-made","The Worst Api Ever Made",[842,95424,95425],{},"The author of the article presents how bad API affects the programmers by manufacturing unnecessary work. In order to evaluate the issues which are created by bad APIs, the author chose the Event Tracing for Windows as an example of a heavyweight API. Not only does the article point out the mistakes made in the Microsoft API (which is overloaded, error-prone, and complicated), but it also presents the principles which should be followed while creating an API. The main rule is to write the usage code in the first step. Starting from this point, you can immediately see how the API will work if it has no constraints. Thanks to it, you can take a user-perspective to create a clear and functional API.",[842,95427,95428],{},[964,95429,95430,95431,861],{},"See the source from Casey Muratori ",[964,95432,95433],{},[846,95434,95437],{"href":95435,"rel":95436},"https://caseymuratori.com/blog_0025",[850],[964,95438,91247],{},[863,95440,95442],{"id":95441},"bulletproof-nodejs-project-architecture","Bulletproof node.js project architecture",[842,95444,95445],{},"Well designed project architecture is the basis for creating flexible, understandable, and maintainable applications. The Bulletproof node.js project architecture article presents the correct organization of the node.js project structure. Following the given rules help programmers to avoid code duplication, improve stability, and scale their services. The author created 6 basic takeaways, which will help you correctly design the project structure.",[842,95447,95448],{},[964,95449,95450,95451,861],{},"See the source from Software on the Road ",[964,95452,95453],{},[846,95454,95457],{"href":95455,"rel":95456},"https://softwareontheroad.com/ideal-nodejs-project-structure/",[850],[964,95458,91247],{},[863,95460,95462],{"id":95461},"async-io-in-python-a-complete-walkthrough","Async IO in Python: A Complete Walkthrough",[842,95464,95465],{},"The Asynchronous IO is a programming model supported by Python, which enables writing concurrent code. The article goes through the topics of async IO and its implementations, new Python keywords - async/await, and the Python package that provides the API to run and manage coroutines. In the beginning, the author compares the concurrent programming models. Furthermore, the main focus is put on the usability of async IO and the APIs that have emerged around it. Thanks to detailed explanations and several examples, the reader can understand the idea and functionality of Asynchronous IO as well as its implementation in Python.",[842,95467,95468],{},[964,95469,95470,95471,861],{},"See the source from Real Python ",[964,95472,95473],{},[846,95474,95477],{"href":95475,"rel":95476},"https://realpython.com/async-io-python/",[850],[964,95478,91247],{},[863,95480,95482],{"id":95481},"the-ultimate-guide-to-handling-jwts-on-frontend-clients-graphql","The Ultimate Guide to handling JWTs on frontend clients (GraphQL)",[842,95484,95485],{},"JSON Web Token (JWT) is an open standard that determines a compact, self-contained, and secure transformation of information between parties as a JSON object. This information is digitally signed, therefore, has become a popular way of handling auth. The article explains a JWT structure, shows the best way of implementation as well as discusses the advantages and disadvantages of using JWT. The post focuses on GraphQL client, but it could be applied to any frontend client. Going through all the steps, you can implement the solutions to your projects to have all the capabilities of a modern application.",[842,95487,95488],{},[964,95489,95490,95491,861],{},"See the source from Hasura Blog ",[964,95492,95493],{},[846,95494,95497],{"href":95495,"rel":95496},"https://blog.hasura.io/best-practices-of-using-jwt-with-graphql/",[850],[964,95498,91247],{},[863,95500,95502],{"id":95501},"angular-2-improve-performance-with-trackby","Angular 2 - Improve Performance with trackBy",[842,95504,95505],{},"The post shows the solution for the problem with which might face the Angular developers while changing the data in the collection without keeping the same reference. The problem arises because Angular does not keep track of items and does not know which objects have been changed. In order to follow which items were added or removed the author suggest to use the trackBy function which resolves this obstacle.",[842,95507,95508],{},[964,95509,95510,95511,861],{},"See the source from Netanel Basal ",[964,95512,95513],{},[846,95514,95517],{"href":95515,"rel":95516},"https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5",[850],[964,95518,91247],{},[863,95520,95522],{"id":95521},"how-to-schedule-the-boring-stuff-with-django-and-celery-beat","How to schedule ‘the Boring Stuff’ with Django and Celery Beat",[842,95524,95525],{},"The article describes the implementation and functionality of the Celery Beat. It is a tool that helps software engineers to schedule their daily tasks, such as generating periodic reports, handling vasty imports or exports, backups, frequent API requests. Moreover, the application enables you to manage your tasks in the Django admin panel without interfering with a code.",[842,95527,95528],{},[964,95529,95530,95531,861],{},"See the source from Merixstudio ",[964,95532,95533],{},[846,95534,95537],{"href":95535,"rel":95536},"https://www.merixstudio.com/blog/django-celery-beat/",[850],[964,95538,91247],{},[863,95540,95542],{"id":95541},"how-to-ensure-code-quality-in-javascript-projects","How to ensure code quality in JavaScript projects",[842,95544,95545],{},"JavaScript is one of the most popular programming languages. In order to keep the high code quality, it is crucial to implement the processes which help you to evaluate the code, eliminate bugs, and maintain a consistent programming standard in the project. In the article, several methods of code evaluation were presented. The author described the tools which enable automatic code formatting, static code analysis, local automation, and continuous integration. The recommended methods improve the quality of the code by increasing its correctness and allowing it to catch simple errors at the level of single lines (mainly their formatting and syntax).",[842,95547,95548],{},[964,95549,95550,95551,861],{},"See the source from Bulldogjob ",[964,95552,95553],{},[846,95554,95557],{"href":95555,"rel":95556},"https://bulldogjob.pl/news/856-jak-zapewnic-jakosc-kodu-w-projektach-javascript",[850],[964,95558,91247],{},[863,95560,95562],{"id":95561},"const-assertions-in-literal-expressions-in-typescript","Const Assertions in Literal Expressions in TypeScript",[842,95564,95565],{},"In Schulz’s article, the new TypeScript feature was described. To understand the motivation of creating const assertions, the author explained the functionality of the string literal types and performing literal type widening in TypeScript. The article presents how the const assertion works, its usability, as well as where and when it could be applied.",[842,95567,95568],{},[964,95569,95570,95571,861],{},"See the source from Marius Schulz ",[964,95572,95573],{},[846,95574,95577],{"href":95575,"rel":95576},"https://mariusschulz.com/blog/const-assertions-in-literal-expressions-in-typescript",[850],[964,95578,91247],{},[863,95580,95582],{"id":95581},"_4-simple-steps-for-debugging-your-arduino-project","4 Simple Steps For Debugging Your Arduino Project",[842,95584,95585],{},"Conducting an Arduino project might be challenging, owing to the fact that it has no debugging system. Therefore, it is essential to learn how to conduct the debugging process in Arduino. Before coming to the main topic of the article, the author shows how to correctly plan the Arduino project. The last phase is debugging which contains of the four steps. Taking those recommendations into account helps Arduino developers to overcome the problems, eliminate errors, and deliver high-quality code.",[842,95587,95588],{},[964,95589,95590,95591,861],{},"See the source from Circuito ",[964,95592,95593],{},[846,95594,95597],{"href":95595,"rel":95596},"https://www.circuito.io/blog/arduino-debugging/",[850],[964,95598,91247],{},[842,95600,95601],{},"During this year, we learned a lot as well as developed our skills. The gathered articles are an example that we never stop looking for new solutions, which deepen our knowledge of software development every day. In the following year, we will systematically expand the database of our articles to share with you our knowledge and interesting insights from the IT world, stay tuned!",[842,95603,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":95605},[95606,95607,95608,95609,95610,95611,95612,95613,95614,95615],{"id":95401,"depth":729,"text":95402},{"id":95421,"depth":729,"text":95422},{"id":95441,"depth":729,"text":95442},{"id":95461,"depth":729,"text":95462},{"id":95481,"depth":729,"text":95482},{"id":95501,"depth":729,"text":95502},{"id":95521,"depth":729,"text":95522},{"id":95541,"depth":729,"text":95542},{"id":95561,"depth":729,"text":95562},{"id":95581,"depth":729,"text":95582},"2020-01-02T00:00:00.000Z","To constantly improve the skills and knowledge of software development, our team has read many articles in the previous year. Discover the list of our top 10.",{"src":95619},"/images/blog/musictechlab_blog_top-10-articles-through-the-eyes-of-our-developers.webp",{"enabled":738,"items":95621},[95622,95624,95626],{"text":95623,"icon":5365},"Top picks span Angular debugging, Node.js architecture, Python async IO, and JWT best practices.",{"text":95625,"icon":1769},"Well-designed project structure prevents code duplication and improves maintainability at scale.",{"text":95627,"icon":3271},"Celery Beat automates recurring tasks like reports, backups, and API polling from Django admin.",{},{"title":650,"description":95617},[18784],"A82QT_8ls-iD57yVjbgE--sOXA_OuWuRRcxh1Cd8Sbw",{"id":95633,"title":470,"authors":95634,"badge":723,"body":95639,"category":756,"client":723,"date":96040,"description":96041,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":96042,"keyTakeaways":96044,"meta":96052,"navigation":738,"path":471,"seo":96053,"status":723,"stem":472,"tags":96054,"teaser":723,"__hash__":96055},"posts/blog/software-development/git-better-1-see-more-with-a-commit-message-convention.md",[95635],{"name":95636,"avatar":95637},"Paweł Glimos",{"src":95638},"/images/people/pawel-glimos.webp",{"type":725,"value":95640,"toc":96026},[95641,95644,95646,95650,95653,95659,95662,95679,95682,95684,95688,95691,95700,95706,95804,95810,95873,95875,95879,95882,95888,95891,95895,95921,95923,95935,95939,95945,95951,95955,95961,95972,95974,95978,95981,96001,96005,96011,96013,96017,96020,96023],[842,95642,95643],{},"Many of us don't pay much attention to commit messages. But when we need to look for a specific change, or we are wondering why an addition was made, problems appear. What if I told you, there is a simple and easy way to greatly improve the experience with git history.",[4937,95645],{},[863,95647,95649],{"id":95648},"troubling-reality","Troubling Reality",[842,95651,95652],{},"Let's be honest, we all have seen (and been a part of) such git logs:",[1013,95654,95657],{"className":95655,"code":95656,"language":1018},[1016],"ad8621a Fix a bug in the feature\n16b36c6 Addressed a PR comment\n23ad9ad You can now download form correctly through the main website\n21672sd Typing for mypy\n",[895,95658,95656],{"__ignoreMap":728},[842,95660,95661],{},"These messages try to describe what changes have been made, but ultimately are not much of use. Why?",[1045,95663,95665,95670,95675],{"className":95664},[1048,1049,1050,1051,1052],[1054,95666],{"description":95667,"icon":95668,"title":95669},"They don't point to the place in the code where the change was made.","i-lucide-map-pin-off","No Location",[1054,95671],{"description":95672,"icon":95673,"title":95674},"They don't specify what type of change was made, making it impossible to filter.","i-lucide-filter-x","No Type",[1054,95676],{"description":95677,"icon":3261,"title":95678},"Each one is written in a different style — no pattern, no structure.","Inconsistent Style",[842,95680,95681],{},"All of these problems may be solved by adopting a git commit message convention into the project, and today I am going to show you one — Karma.",[4937,95683],{},[863,95685,95687],{"id":95686},"what-is-karma","What Is Karma?",[842,95689,95690],{},"Today, we are not talking about famous religious-based \"you get what you do\". However, that analogy is quite fitting as karma will sooner or later get you, if you are not using proper commit messages.",[842,95692,95693,95694,95699],{},"The Karma I want to show you is a git commit message convention, birthed from the ",[846,95695,95698],{"href":95696,"rel":95697},"https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#",[850],"AngularJS Git Commit Conventions google document",", adapted to be used with any project and language. It aims to provide more useful, clear and precise information when browsing the git history, while also helping to easily achieve other goals:",[842,95701,95702,95705],{},[996,95703,95704],{},"Automatically generate changelog"," — When your git messages follow a convention, it is much easier to generate a changelog:",[1013,95707,95709],{"className":1080,"code":95708,"language":1082,"meta":728,"style":728},"git log \u003Clast tag> HEAD --pretty=format:%s\n# All commits since the last release\n\n23ad9ad feat(proxy): Added support for bi-directional streams\n21672sd fix(proto): Fixed missing type annotation\n",[895,95710,95711,95736,95741,95745,95776],{"__ignoreMap":728},[1086,95712,95713,95715,95717,95719,95722,95725,95728,95730,95733],{"class":1088,"line":1089},[1086,95714,1093],{"class":1092},[1086,95716,33499],{"class":1096},[1086,95718,38664],{"class":1146},[1086,95720,95721],{"class":1096},"last",[1086,95723,95724],{"class":1096}," ta",[1086,95726,95727],{"class":1436},"g",[1086,95729,2694],{"class":1146},[1086,95731,95732],{"class":1096}," HEAD",[1086,95734,95735],{"class":1096}," --pretty=format:%s\n",[1086,95737,95738],{"class":1088,"line":729},[1086,95739,95740],{"class":1427},"# All commits since the last release\n",[1086,95742,95743],{"class":1088,"line":1112},[1086,95744,3390],{"emptyLinePlaceholder":738},[1086,95746,95747,95750,95753,95755,95758,95760,95762,95765,95768,95770,95773],{"class":1088,"line":1181},[1086,95748,95749],{"class":1092},"23ad9ad",[1086,95751,95752],{"class":1096}," feat",[1086,95754,1398],{"class":1146},[1086,95756,95757],{"class":1092},"proxy",[1086,95759,1410],{"class":1146},[1086,95761,1133],{"class":1096},[1086,95763,95764],{"class":1096}," Added",[1086,95766,95767],{"class":1096}," support",[1086,95769,86137],{"class":1096},[1086,95771,95772],{"class":1096}," bi-directional",[1086,95774,95775],{"class":1096}," streams\n",[1086,95777,95778,95781,95784,95786,95789,95791,95793,95796,95799,95801],{"class":1088,"line":1205},[1086,95779,95780],{"class":1092},"21672sd",[1086,95782,95783],{"class":1096}," fix",[1086,95785,1398],{"class":1146},[1086,95787,95788],{"class":1092},"proto",[1086,95790,1410],{"class":1146},[1086,95792,1133],{"class":1096},[1086,95794,95795],{"class":1096}," Fixed",[1086,95797,95798],{"class":1096}," missing",[1086,95800,41948],{"class":1096},[1086,95802,95803],{"class":1096}," annotation\n",[842,95805,95806,95809],{},[996,95807,95808],{},"Filter out unimportant commits"," — Using unified descriptions we can easily look through the history and pick only the interesting ones:",[1013,95811,95813],{"className":1080,"code":95812,"language":1082,"meta":728,"style":728},"git log \u003Clast release> HEAD --grep feat\n# All new features since the last release\n\n23ad9ad feat(proxy): Added support for bi-directional streams\n",[895,95814,95815,95840,95845,95849],{"__ignoreMap":728},[1086,95816,95817,95819,95821,95823,95825,95828,95830,95832,95834,95837],{"class":1088,"line":1089},[1086,95818,1093],{"class":1092},[1086,95820,33499],{"class":1096},[1086,95822,38664],{"class":1146},[1086,95824,95721],{"class":1096},[1086,95826,95827],{"class":1096}," releas",[1086,95829,39081],{"class":1436},[1086,95831,2694],{"class":1146},[1086,95833,95732],{"class":1096},[1086,95835,95836],{"class":1096}," --grep",[1086,95838,95839],{"class":1096}," feat\n",[1086,95841,95842],{"class":1088,"line":729},[1086,95843,95844],{"class":1427},"# All new features since the last release\n",[1086,95846,95847],{"class":1088,"line":1112},[1086,95848,3390],{"emptyLinePlaceholder":738},[1086,95850,95851,95853,95855,95857,95859,95861,95863,95865,95867,95869,95871],{"class":1088,"line":1181},[1086,95852,95749],{"class":1092},[1086,95854,95752],{"class":1096},[1086,95856,1398],{"class":1146},[1086,95858,95757],{"class":1092},[1086,95860,1410],{"class":1146},[1086,95862,1133],{"class":1096},[1086,95864,95764],{"class":1096},[1086,95866,95767],{"class":1096},[1086,95868,86137],{"class":1096},[1086,95870,95772],{"class":1096},[1086,95872,95775],{"class":1096},[4937,95874],{},[863,95876,95878],{"id":95877},"how-to-use-it","How to Use It?",[842,95880,95881],{},"The general structure of a Karma based message is:",[1013,95883,95886],{"className":95884,"code":95885,"language":1018},[1016],"\u003Ctype>(\u003Cscope>): \u003Csubject>\n\n\u003Cbody>\n\n\u003Cfooter>\n",[895,95887,95885],{"__ignoreMap":728},[842,95889,95890],{},"It is really simple to remember and once you start using it, you will almost never have to consult the reference. Each line can have a maximum of 80 characters and the second line must always be blank.",[1074,95892,95894],{"id":95893},"allowed-types","Allowed Types",[1045,95896,95898,95902,95906,95910,95913,95917],{"className":95897},[1048,50574,1050,1051,1052],[1054,95899],{"description":95900,"icon":12409,"title":95901},"New features, or ones that changed behaviour.","feat",[1054,95903],{"description":95904,"icon":64001,"title":95905},"Bug fixes in ready features.","fix",[1054,95907],{"description":95908,"icon":8790,"title":95909},"Documentation, both in and outside code.","docs",[1054,95911],{"description":95912,"icon":87822,"title":1680},"Formatting, typos — no code change.",[1054,95914],{"description":95915,"icon":52268,"title":95916},"Refactoring production code.","refactor",[1054,95918],{"description":95919,"icon":87263,"title":95920},"All things test related.","test",[1074,95922,84290],{"id":19992},[842,95924,95925,95926,5660,95929,5660,95931,95934],{},"The scope is a description of what part of the code was affected — for example: ",[895,95927,95928],{},"service",[895,95930,95757],{},[895,95932,95933],{},"runner",", etc.",[1074,95936,95938],{"id":95937},"body-footer","Body & Footer",[842,95940,95941,95942,95944],{},"In the ",[996,95943,24185],{},", include a sentence on why the changes were made. Be descriptive! If someone looked this far inside your commit, they will appreciate every bit of information they can find.",[842,95946,95941,95947,95950],{},[996,95948,95949],{},"footer",", you can reference issues. Multiple issues may be used at the same time.",[1074,95952,95954],{"id":95953},"full-example","Full Example",[1013,95956,95959],{"className":95957,"code":95958,"language":1018},[1016],"fix(service): Verify the content type that is returned to the adapter\n\nAPI can respond with text/plain content type instead of JSON.\nTo ensure proper operation it is verified before further processing.\n\nCloses #112\n",[895,95960,95958],{"__ignoreMap":728},[1032,95962,95963],{},[842,95964,95965,95966,95971],{},"Further and more precise explanations can be found on the official ",[846,95967,95970],{"href":95968,"rel":95969},"https://karma-runner.github.io/4.0/dev/git-commit-msg.html",[850],"Karma website",", which I highly recommend as a reference.",[4937,95973],{},[863,95975,95977],{"id":95976},"why-should-i-bother-if-i-never-use-git-log","Why Should I Bother If I Never Use Git Log?",[842,95979,95980],{},"You may say: \"What you wrote looks nice, but I honestly can't remember the last time I actually used git log.\" And I have to agree — during everyday work it rarely happens that we need to use git log extensively. Here are some additional thoughts:",[1045,95982,95984,95988,95993,95997],{"className":95983},[1048,1049,1765,1051,1052],[1054,95985],{"description":95986,"icon":7495,"title":95987},"Good commit messages are like backup — you think you don't need one until you do.","Like Backup",[1054,95989],{"description":95990,"icon":95991,"title":95992},"As long as you state the rules and stick to them, it will be beneficial. Even just the first line of Karma is enough in most scenarios.","i-lucide-sliders","Fully Customizable",[1054,95994],{"description":95995,"icon":9656,"title":95996},"Think about a situation where you take over a project. Which kind of commit messages would you prefer? Maintain your project so you're not ashamed when someone else reads it.","Handover-Ready",[1054,95998],{"description":95999,"icon":7560,"title":96000},"Maybe you would be using your git history more if it was better structured, not the other way around.","Better History = More Usage",[1074,96002,96004],{"id":96003},"convention-levels","Convention Levels",[1013,96006,96009],{"className":96007,"code":96008,"language":1018},[1016],"minimal    fix(service): Verify the content type returned to the adapter\n\nambitious  API can respond with text/plain content type instead of JSON.\n           To ensure proper operation it is verified before further processing.\n\nfull       Fixes #112\n",[895,96010,96008],{"__ignoreMap":728},[4937,96012],{},[863,96014,96016],{"id":96015},"closing-off","Closing Off",[842,96018,96019],{},"If you are convinced, share the idea with your team and try sticking to Karma rules for your next project. You can also convert to the convention in an ongoing one. Changing git messages will rarely break anything in your environment, so it doesn't hurt to try — it is never too late to Git Better.",[842,96021,96022],{},"This article is a part of the ongoing series \"Git Better with MusicTech Lab\". In the next one, we are gonna cover cherry-picking — when it is a good practice and how to avoid common errors using it.",[1680,96024,96025],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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);}",{"title":728,"searchDepth":729,"depth":729,"links":96027},[96028,96029,96030,96036,96039],{"id":95648,"depth":729,"text":95649},{"id":95686,"depth":729,"text":95687},{"id":95877,"depth":729,"text":95878,"children":96031},[96032,96033,96034,96035],{"id":95893,"depth":1112,"text":95894},{"id":19992,"depth":1112,"text":84290},{"id":95937,"depth":1112,"text":95938},{"id":95953,"depth":1112,"text":95954},{"id":95976,"depth":729,"text":95977,"children":96037},[96038],{"id":96003,"depth":1112,"text":96004},{"id":96015,"depth":729,"text":96016},"2019-12-27T00:00:00.000Z","Using a consistent style of commit messages can increase the effectiveness of a team that develops bespoke software.",{"src":96043},"/images/blog/musictechlab_blog_git-better-1-see-more-with-a-commit-message-convention.webp",{"enabled":738,"items":96045},[96046,96048,96050],{"text":96047,"icon":1779},"Karma convention adds type and scope to every commit for instant context.",{"text":96049,"icon":1723},"Structured messages enable auto-generated changelogs with one git command.",{"text":96051,"icon":5365},"Filtering by type (feat, fix, chore) makes git history instantly searchable.",{},{"title":470,"description":96041},[15279,18784],"QP6th8QrePf6Wdk3hdfaocqMZbCq4aW2gCDsO0CapMU",{"id":96057,"title":482,"authors":96058,"badge":723,"body":96061,"category":756,"client":723,"date":96040,"description":96187,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":96188,"keyTakeaways":96190,"meta":96200,"navigation":738,"path":483,"seo":96201,"status":723,"stem":484,"tags":96202,"teaser":723,"__hash__":96203},"posts/blog/software-development/how-does-javascript-work.md",[96059],{"name":87508,"avatar":96060},{"src":87797},{"type":725,"value":96062,"toc":96184},[96063,96065,96071,96074,96083,96086,96094,96097,96101,96115,96118,96125,96128,96134,96148,96156,96159,96166,96173,96176,96179,96182],[842,96064,40867],{},[842,96066,96067,96068,861],{},"As you probably heard, JavaScript is a single-thread programming language. It means processed operations are handled one by one. JavaScript doesn’t have the ability to make operations parallel. We can do it in a programming language like Java, where we have the possibility to run several calculations in multiple threads. JavaScript cannot do that, it processes all the operations in sequence, so it might be very slow. But it isn’t! All of you are using very complex websites and huge web applications like Facebook. At the same time, the browser is downloading posts into our wall, we are writing and sending messages, displaying our friends’ images, clicking “Like” under them, and we don’t have any delays. It is all because of the mechanism called ",[996,96069,96070],{},"EventLoop",[842,96072,96073],{},"​ ​",[842,96075,96076,96077,96082],{},"The Event Loop mechanism is closely related to the browser call stack. Call Stack is a place where goes all events created in a JavaScript application. Let’s imagine the situation when we have a button with an ",[964,96078,96079],{},[996,96080,96081],{},"onClick"," event listener and a method that increments a counter.",[842,96084,96085],{},"$.on('button', 'click', function onClick() {\ncounter += 1;\n});",[842,96087,96088,96089,96093],{},"After clicking on this button, the invocation of the ",[964,96090,96091],{},[996,96092,96081],{}," method is put on the call stack. It is only a simple example but in real life, we have a huge amount of events that end up on call stack. Call stack is a data structure that follows the LIFO (Last In - First Out) rule. It means when we want to get values from the stack, we always have to take them from the top of the stack and go from up to down.",[842,96095,96096],{},"Event Loop also depends on the browser engine. Each browser engine has its own implementation of the event loop, so they may be some differences in event processing between V8, Gecko, etc. In this article, we focused on the V8 engine used in the Chrome browser and we will present event loop implementation in V8.",[863,96098,96100],{"id":96099},"event-loop-behavior","Event Loop behavior",[842,96102,96103,96104,96107,96108,28366,96111,96114],{},"In JavaScript, we can distinguish synchronous and asynchronous operations. Synchronous operations are run in the order in which they were called and they have priority. Asynchronous operations seem to be working in the background of the application, but it’s only an illusion. In the JavaScript call stack, there are put events related to synchronous operations and related to ",[996,96105,96106],{},"callbacks",", to asynchronous ones, e.g. ",[964,96109,96110],{},"setTimeout",[964,96112,96113],{},"Promise.prototype.then","callback methods.",[842,96116,96117],{},"In the Event Loop mechanism, we have 3 queues: microtask queue, render queue, and task queue. Queues are working in consistence with FIFO (First In - First Out) rule. When events are removed from the call stack, they go through the whole loop.",[842,96119,96120,96121,96124],{},"The first checkpoint for them is a microtask queue. We put there all callbacks to methods for resolving and rejecting of ",[964,96122,96123],{},"Promises",". The microtask queue performs all operations until it is empty. After that, the rest of the data from the call stack goes to the next stop - the render queue.",[842,96126,96127],{},"The render queue collects all tasks that need to be done before another application rendering. It is dealing with, e.g. animations. The render queue behavior is the same as the microtask queue. It evokes all operations and finishes its work when there are no more tasks.",[842,96129,96130,96131,96133],{},"The last element of the Event Loop sequence is the Task Queue. This queue is responsible for collecting all the callbacks to WebAPI methods like ",[964,96132,96110],{},". The behavior of Task Queue is a little bit different than the previous queues. It proceeds only with one element, performs it, and after that, the Event Loop switches back to the call stack. So even if there are some tasks left on Task Queue, it can execute only one operation.",[958,96135,96136,96139,96142,96145],{},[961,96137,96138],{},"Take events from the call stack until it is empty.",[961,96140,96141],{},"Perform subsequent operations from the Microtask Queue until it is empty.",[961,96143,96144],{},"Perform subsequent operations from the Render Queue until it is empty.",[961,96146,96147],{},"Perform one operation from the Task Queue.",[842,96149,96150,96151,1133],{},"You can also check the Event Loop behavior by running this code on ",[846,96152,96155],{"href":96153,"rel":96154},"https://stackblitz.com/",[850],"Stackblitz",[842,96157,96158],{},"function log(arg) {\nconsole.log(arg);\n}",[842,96160,96161,96162,96165],{},"function logTimeout(number, callback) {\nsetTimeout(function() {\ncallback(",[895,96163,96164],{},"Timeout ${number}",");\n}, 0);\n}",[842,96167,96168,96169,96172],{},"function logPromise(number) {\nreturn Promise.resolve(",[895,96170,96171],{},"Promise ${number}",");\n}",[842,96174,96175],{},"logPromise(1).then(log);\nlogPromise(2).then(log);\nlogTimeout(1, log);\nrequestAnimationFrame(function() {\nlog('RequestAnimationFrame 1');\n});\nrequestAnimationFrame(function() {\nlog('RequestAnimationFrame 2');\n});",[842,96177,96178],{},"for(let i = 0; i \u003C 999999; i++) {};\nconsole.log('The end');",[842,96180,96181],{},"Being aware of how JavaScript works can help you in your daily work. Knowing that JavaScript is single-threaded can allow you to choose the right implementation and to solve many of the hazards you encountered while working with JavaScript.",[842,96183,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":96185},[96186],{"id":96099,"depth":729,"text":96100},"Knowing the Event Loop mechanism allows you to choose the right implementation and to solve many of the hazards you encountered while working with JavaScript.",{"src":96189},"/images/blog/musictechlab_blog_how-does-javascript-work.webp",{"enabled":738,"items":96191},[96192,96194,96196,96198],{"text":96193,"icon":8500},"JavaScript is single-threaded but handles concurrency through the Event Loop mechanism.",{"text":96195,"icon":1769},"Event Loop has three queues: microtask, render, and task, processed in that order.",{"text":96197,"icon":3271},"Task Queue executes only one operation per loop cycle, unlike microtask and render queues.",{"text":96199,"icon":5365},"Each browser engine (V8, Gecko) has its own Event Loop implementation.",{},{"title":482,"description":96187},[18784],"fmq3ulLSC0xYFnGWHCP26etHXvHaGUoTPsojG5T4odc",{"id":96205,"title":534,"authors":96206,"badge":723,"body":96209,"category":756,"client":723,"date":96327,"description":96328,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":96329,"keyTakeaways":96331,"meta":96339,"navigation":738,"path":535,"seo":96340,"status":723,"stem":536,"tags":96341,"teaser":723,"__hash__":96342},"posts/blog/software-development/important-new-features-in-python-3-8.md",[96207],{"name":95636,"avatar":96208},{"src":95638},{"type":725,"value":96210,"toc":96320},[96211,96213,96216,96220,96223,96227,96236,96239,96243,96246,96250,96259,96262,96266,96269,96273,96288,96292,96303,96307,96315,96318],[842,96212,40867],{},[842,96214,96215],{},"The recent Python 3.8 release introduced many interesting new features, optimizations and tweaks. As new Python versions are published, developers must stay up to date with language changes. In this article, we will try to present the most exciting changes.",[863,96217,96219],{"id":96218},"assignment-operator","Assignment operator",[842,96221,96222],{},"The assignment operator lets you create value assignments within larger logic expressions.",[842,96224,96225],{},[964,96226,7741],{},[41054,96228,96229],{},[41054,96230,96231],{},[41054,96232,96233],{},[842,96234,96235],{},"if ( value := 1 + 1 ) > 1: # 2 > 1\nprint(value)\n2",[842,96237,96238],{},"In this example, we assign 1+1 to a variable (value) and check if it is greater than 1. It is important to note that you can also use an assignment operator inside list comprehension.",[863,96240,96242],{"id":96241},"new-f-strings-specifier","New f-strings specifier",[842,96244,96245],{},"Python 3.8 introduces a new ‘=’ specifier available to use inside f-string expressions. The specifier will automatically get the object name with the object value paired.",[842,96247,96248],{},[964,96249,7741],{},[41054,96251,96252],{},[41054,96253,96254],{},[41054,96255,96256],{},[842,96257,96258],{},"object='example_value'\nf'{object}'\n'example_value'\nf'object={object}'\n'object=example_value'\nf'{object=}'\n\"object='example_value'\"\nf'{object=!s}'\n'object=example_value'",[842,96260,96261],{},"This new feature will certainly be helpful in testing and logging. As you can see in the example, it is also possible to use this conversion flag with the new specifier.",[863,96263,96265],{"id":96264},"new-importlibmetadata-module","New importlib.metadata module",[842,96267,96268],{},"New importlib.metadata module can help you read metadata, such as the version number for packages installed with tools like pip. Let’s check the installed version of Django and its requirements.",[842,96270,96271],{},[964,96272,7741],{},[41054,96274,96275],{},[41054,96276,96277],{},[41054,96278,96279,96282],{},[842,96280,96281],{},"from importlib import metadata\nmetadata.version('django')\n'3.0'",[842,96283,96284,96285],{},"metadata.requires('django')\n",[1086,96286,96287],{},"'pytz', 'sqlparse (>=0.2.2)', 'asgiref (~=3.2)', \"argon2-cffi (>=16.1.0) ; extra == 'argon2'\", \"bcrypt ; extra == 'bcrypt'\"",[863,96289,96291],{"id":96290},"removed-features-in-python-38","Removed features in Python 3.8",[958,96293,96294,96297,96300],{},[961,96295,96296],{},"The macpath module",[961,96298,96299],{},"isAlive() method of threading.Thread",[961,96301,96302],{},"platform.popen() method",[863,96304,96306],{"id":96305},"deprecated-features-in-python-38","Deprecated features in Python 3.8",[958,96308,96309,96312],{},[961,96310,96311],{},"getchildren() and getiterator() methods of ElementTree",[961,96313,96314],{},"lgettext(), ldgettext(), lngettext() and ldngettext() of gettext module",[842,96316,96317],{},"To sum up, if your career path includes Python development, it is worth keeping up to date with python releases systematically. Check out our blog for more news.",[842,96319,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":96321},[96322,96323,96324,96325,96326],{"id":96218,"depth":729,"text":96219},{"id":96241,"depth":729,"text":96242},{"id":96264,"depth":729,"text":96265},{"id":96290,"depth":729,"text":96291},{"id":96305,"depth":729,"text":96306},"2019-12-13T00:00:00.000Z","The new release of the Python programming language brought new features that might be very useful while providing python web development services.",{"src":96330},"/images/blog/musictechlab_blog_important-new-features-in-python-3-8.webp",{"enabled":738,"items":96332},[96333,96335,96337],{"text":96334,"icon":5365},"The walrus operator (:=) enables inline assignment inside expressions like if-statements.",{"text":96336,"icon":1723},"New f-string '=' specifier auto-prints variable names with values for easier debugging.",{"text":96338,"icon":12412},"importlib.metadata lets you read installed package versions and dependencies at runtime.",{},{"title":534,"description":96328},[18784],"3jefKp8jzpo0FFp4pVCGA0xCOIR-xy443Xeu4E4vfLI",{"id":96344,"title":578,"authors":96345,"badge":723,"body":96348,"category":756,"client":723,"date":96467,"description":96468,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":69,"image":96469,"keyTakeaways":96471,"meta":96479,"navigation":738,"path":579,"seo":96480,"status":723,"stem":580,"tags":96481,"teaser":723,"__hash__":96482},"posts/blog/software-development/migrating-from-travisci-to-github-actions.md",[96346],{"name":90132,"avatar":96347},{"src":94277},{"type":725,"value":96349,"toc":96460},[96350,96353,96357,96364,96374,96378,96389,96392,96394,96397,96400,96403,96406,96409,96411,96414,96433,96438,96442,96457],[842,96351,96352],{},"Every project needs CI and there are many solutions to choose from. We'll compare two of them by describing our journey migrating from TravisCI to GitHub Actions.",[863,96354,96356],{"id":96355},"what-even-are-these-tools","What even are these tools?",[842,96358,96359,96360,96363],{},"Both TravisCI and GitHub Actions are continuous integration services. ",[996,96361,96362],{},"Continuous Integration (CI)"," requires developers to frequently integrate code into a shared repository. Each change is verified by automated tools — linting, tests, and builds — catching problems early.",[842,96365,96366,96367,96370,96371,861],{},"CI can be paired with ",[996,96368,96369],{},"Continuous Delivery (CD)",", which ensures software can be deployed at any time. Automate the release process further and you get ",[996,96372,96373],{},"continuous deployment",[863,96375,96377],{"id":96376},"why-are-we-changing","Why are we changing?",[1045,96379,96381,96385],{"className":96380},[1048,1049,1765,1051,1052],[1054,96382],{"description":96383,"title":96384},"$129/month — unlimited build time, up to 2 concurrent jobs","TravisCI Startup",[1054,96386],{"description":96387,"title":96388},"Included in $88/month GitHub Team — 10,000 minutes, up to 60 concurrent jobs","GitHub Actions (with Team)",[842,96390,96391],{},"We were using about 5,500 minutes of builds per month — well within GitHub’s free limit. Even paying for all that time would cost only $44. The decision was simple.",[863,96393,94781],{"id":94780},[842,96395,96396],{},"Both tools are similar in concept: do a couple of things when something happens and report the results. For both tools, specifying what happens is done via special YAML files stored in the repository along with the project source code. Setting when something happens on TravisCI is done by pushing the correct button on the website. That’s different for Github Actions: you set it in the YAML configuration file.",[842,96398,96399],{},"The concept of determining what to do and in which order is similar in both tools, but the implementation is different. In TravisCI, you can add blocks of code to predefined steps that are executed in a specific order. In Github Actions, you can create any number of actions, which contain jobs with inside steps (blocks of code). We created one action that is composed of three jobs: testing, building, and deployment. Contents of TravisCI steps were distributed among these jobs in a form of steps. Focusing deployment step only on specific branches and parametrizing target was easy because both platforms support conditional step execution in their configuration format.",[842,96401,96402],{},"Specifying the required environment is also similar — set languages and versions in the config file and everything is taken care of. Both platforms support multiple operating systems and matrix builds. One difference: repository contents are always available in TravisCI, but GitHub Actions requires an explicit checkout step.",[842,96404,96405],{},"Handling secrets works the same way — create them on the website and they become environment variables in the build. Both tools sanitize output by censoring secrets if printed.",[842,96407,96408],{},"The build badge was also straightforward to migrate. Both platforms expose build status as an image URL you can embed in READMEs and Slack channels.",[863,96410,94814],{"id":94813},[842,96412,96413],{},"Not everything is 100% compatible. Here are the issues we hit:",[1045,96415,96417,96421,96425,96429],{"className":96416},[1048,1049,1765,1051,1052],[1054,96418],{"description":96419,"title":96420},"TravisCI has `BRANCH`. GitHub Actions has `GITHUB_REF` which includes extra data — we had to extract the branch name from the ref string.","Branch Name",[1054,96422],{"description":96423,"title":96424},"TravisCI provides `TRAVIS_BUILD_NUMBER`. GitHub Actions has no equivalent — we count commits on the branch instead.","Build Number",[1054,96426],{"description":96427,"title":96428},"Each step runs in a separate environment. The workaround: print values to stdout in a specific format to set global env vars.","Passing Values Between Steps",[1054,96430],{"description":96431,"title":96432},"TravisCI lets you SSH into a failed build. GitHub Actions had no equivalent — we resorted to printf-debugging.","No Build Debugging",[1901,96434,96435],{},[842,96436,96437],{},"At the time of writing, GitHub Actions lacked automatic build cancellation. In TravisCI, pushing a new commit cancels the in-progress build on the same branch. GitHub Actions ran all builds, wasting minutes on intermediate commits.",[863,96439,96441],{"id":96440},"does-it-even-work","Does it even work?",[1045,96443,96445,96449,96453],{"className":96444},[1048,1049,1050,1051,1052],[1054,96446],{"description":96447,"title":96448},"Total time to learn and migrate two projects","4 hours",[1054,96450],{"description":96451,"title":96452},"Savings from dropping TravisCI","$1,500/year",[1054,96454],{"description":96455,"title":96456},"Config grew due to GitHub's more explicit format","45 → 60 lines",[842,96458,96459],{},"Execution times are about the same, but builds spin up faster. Despite some missing features, GitHub Actions is mature enough for production use.",{"title":728,"searchDepth":729,"depth":729,"links":96461},[96462,96463,96464,96465,96466],{"id":96355,"depth":729,"text":96356},{"id":96376,"depth":729,"text":96377},{"id":94780,"depth":729,"text":94781},{"id":94813,"depth":729,"text":94814},{"id":96440,"depth":729,"text":96441},"2019-12-06T00:00:00.000Z","A practical comparison of GitHub Actions vs TravisCI with a step-by-step migration guide. Learn why we switched and how to move your CI/CD pipeline smoothly.",{"src":96470},"/images/blog/musictechlab_blog_migrating-from-travisci-to-github-actions.webp",{"enabled":738,"items":96472},[96473,96475,96477],{"text":96474,"icon":3271},"Migrating two projects from TravisCI to GitHub Actions took only 4 hours total.",{"text":96476,"icon":5504},"The switch saved $1,500 per year by using GitHub Actions minutes included in the Team plan.",{"text":96478,"icon":37696},"GitHub Actions config is more explicit (45 to 60 lines) but builds spin up faster.",{},{"title":578,"description":96468},[15279,18784],"R5XO18JHnUgeNeP1kYF1atA9K5mlizVlRLS1lSnZZ3s",{"id":96484,"title":626,"authors":96485,"badge":723,"body":96490,"category":756,"client":723,"date":96562,"description":96563,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":96564,"keyTakeaways":96566,"meta":96574,"navigation":738,"path":627,"seo":96575,"status":723,"stem":628,"tags":96576,"teaser":723,"__hash__":96577},"posts/blog/software-development/thanks-app-a-management-3-0-solution.md",[96486],{"name":96487,"avatar":96488},"Wojtek Ryrych",{"src":96489},"/images/people/wojtek-ryrych.webp",{"type":725,"value":96491,"toc":96557},[96492,96494,96496,96499,96501,96516,96518,96544,96555],[842,96493,40867],{},[863,96495,52035],{"id":52034},[842,96497,96498],{},"Running a software agency is a dynamic business. People work for different clients and various projects. Communication takes place mostly in slack. People work in the same environment, together, but apart. Such dynamics can put an agency on the risk when peoples' engagement drops. Agency's role is to foster the environment that supports growth. How to ensure engagement in teams, connect people and support a healthy atmosphere?",[863,96500,27663],{"id":52066},[842,96502,96503,96504,96507,96508,96511,96512,96515],{},"What do you feel when you receive a gift? Saying a thank you is a form of an appreciation of someone’s work. ",[846,96505,88176],{"href":88520,"rel":96506},[850]," is a software house that embraced the ",[964,96509,96510],{},"Management 3.0"," approach. One of the practices of Management 3.0 is public recognition of colleagues that contribute to the organization. Sincere ",[964,96513,96514],{},"thanks"," brings out a ripple effect that affects the whole surrounding. If appreciation is contagious, why not allow people to say thank you from the tools they use? We did so at Bravelab and the outcome was very positive.",[863,96517,52145],{"id":52144},[842,96519,96520,96521,96523,96524,96527,96528,96531,96532,96535,96536,96539,96540,96543],{},"As we communicate over ",[964,96522,72283],{},", we decided to allow people to appreciate each other right within the application. ",[996,96525,96526],{},"Thanks-app"," - our solution is a Slack app that allows people to say warm words from any channel.\nFor example, when Simon types /thanks @mariusz for birthday cake! ",[996,96529,96530],{},"two things happen",". The message ",[996,96533,96534],{},"lands in #thanks channel",". ",[964,96537,96538],{},"For birthdays cake!"," also appears in a ",[996,96541,96542],{},"web dashboard",". The dashboard displays recent entries and top collectors. Another advantage of the web dashboard is accessibility from any browser.",[842,96545,96546,96547,96550,96551,96554],{},"Where we successful? Sure! Since 2018 we've sent ",[996,96548,96549],{},"over 1000"," warm words. Simple ",[964,96552,96553],{},"Birthdays cake"," can not only spark a chit-chat at the coffee machine. Over time as people get into the habit of appreciation of each other, bonds between them make stronger. Strong bonds mean connected and engaged people. Engagement turns into a synergy that flows through the entire company. Thank you.",[842,96556,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":96558},[96559,96560,96561],{"id":52034,"depth":729,"text":52035},{"id":52066,"depth":729,"text":27663},{"id":52144,"depth":729,"text":52145},"2019-11-19T00:00:00.000Z","Developing a custom solution for the communication platform in order to tighten the relationship between team members.",{"src":96565},"/images/blog/musictechlab_blog_thanks-app-a-management-3-0-solution.webp",{"enabled":738,"items":96567},[96568,96570,96572],{"text":96569,"icon":72284},"Over 1,000 thank-you messages were sent via the Slack app since launching in 2018.",{"text":96571,"icon":1723},"A simple /thanks command posts appreciation to a public channel and a web dashboard.",{"text":96573,"icon":4845},"Public recognition builds stronger team bonds and engagement across distributed teams.",{},{"title":626,"description":96563},[74615],"wiisa9hUzR44sGTHfOxbbrJRFQMtD2kMK2RnAhkmLKo",{"id":96579,"title":370,"authors":96580,"badge":723,"body":96583,"category":756,"client":723,"date":96647,"description":96648,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":96649,"keyTakeaways":96651,"meta":96659,"navigation":738,"path":371,"seo":96660,"status":723,"stem":372,"tags":96661,"teaser":723,"__hash__":96662},"posts/blog/software-development/bravelab-io-named-top-software-developer-in-poland-by-clutch.md",[96581],{"name":93723,"avatar":96582},{"src":93725},{"type":725,"value":96584,"toc":96645},[96585,96587,96590,96599,96602,96615,96621,96635,96643],[842,96586,40867],{},[842,96588,96589],{},"In this year, our company appeared in clutch.co ranking, which is the most popular B2B research and rating platform for the IT industry.",[842,96591,96592,96593,96598],{},"In any startup, the first priority is driving business and attaining a secure and reliable customer base. Although there are several tips and tricks that are great for ensuring early success, the only way to maintain that excellence is by mastering and refining your digital presence. The big fears may be disrupting workflows with timely ",[846,96594,96597],{"href":96595,"rel":96596},"https://www.ideo.com/blog/5-tips-for-running-a-successful-design-sprint",[850],"development sprints",", or working with unreliable development partners. If you’re looking for a surefire investment back into your company, take the guesswork out, and give Bravelab.io a call!",[842,96600,96601],{},"At Bravelab.io we build high-quality software solutions of your dreams. We're capable of building intuitive apps across various business domains. Our work encompasses support at every stage of the development process, from MVP to final product. We always work in close contact with your team to ensure every detail is to your liking!",[842,96603,96604,96605,96608,96609,96614],{},"In light of our efforts, we’ve been named a T",[996,96606,96607],{},"o","p Software Development Partner by Clutch, ",[846,96610,96613],{"href":96611,"rel":96612},"https://clutch.co/pl/developers/krakow",[850],"a B2B platform that helps"," buyers select the right vendors for their projects. We’d like to take this time to thank our customers for participating in client interviews on our behalf to gauge our impact. In reflection of those scores, we’ve ranked a phenomenal 4.9 out of 5-stars! Please take a look at one of our recent reviews below to see what our clients are saying:",[842,96616,96617],{},[1027,96618],{"alt":96619,"src":96620},"Bravelab client review on Clutch platform","/images/blog/musictechlab_blog_clutch-poland.webp",[842,96622,96623,96624,96629,96630,861],{},"Clutch is a B2B market research firm that uses a unique ratings methodology to rank customers across industries. Other honors we’ve received include ",[846,96625,96628],{"href":96626,"rel":96627},"https://themanifest.com/pl/software-development/companies",[850],"where we are on The Manifest"," and their list of top software development companies. Visual Objects, a company portfolio site, also lists us on their one of their ",[846,96631,96634],{"href":96632,"rel":96633},"https://visualobjects.com/pl/software-development/top-custom-software-developers",[850],"best B2B business guides",[842,96636,96637,96638,96642],{},"Once again, thank you to our customers and to the Clutch team for making this honor possible! Please ",[846,96639,96641],{"href":95134,"rel":96640},[850],"contact us today"," if you’d like help bringing your company up to speed on the latest and greatest technologies the industry has to offer.",[842,96644,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":96646},[],"2019-11-15T00:00:00.000Z","Clutch named Bravelab.io as a top custom software development company in Poland 2019. We are recognized as a trusted partner in web development services.",{"src":96650},"/images/blog/musictechlab_blog_musictechlab-io-named-top-software-developer-in-poland-by-clutch.webp",{"enabled":738,"items":96652},[96653,96655,96657],{"text":96654,"icon":3920},"Clutch named Bravelab.io a top software development partner in Poland for 2019.",{"text":96656,"icon":4845},"Client interviews produced a 4.9 out of 5 star rating on the Clutch platform.",{"text":96658,"icon":1067},"Also recognized on The Manifest and Visual Objects as a top developer.",{},{"title":370,"description":96648},[74615],"qUpONKtfc94D80UoRU5WLv_gnH8_U_4kz2kv1U_Bs1k",{"id":96664,"title":610,"authors":96665,"badge":723,"body":96668,"category":756,"client":723,"date":96705,"description":94932,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":96706,"keyTakeaways":96708,"meta":96716,"navigation":738,"path":611,"seo":96717,"status":723,"stem":612,"tags":96718,"teaser":723,"__hash__":96719},"posts/blog/software-development/scratch-me-a-simple-chrome-extension-which-will-increase-your-productivity.md",[96666],{"name":89504,"avatar":96667},{"src":94895},{"type":725,"value":96669,"toc":96700},[96670,96672,96676,96679,96683,96686,96690,96698],[842,96671,40867],{},[863,96673,96675],{"id":96674},"where-did-that-idea-come-from","Where did that idea come from?",[842,96677,96678],{},"Facebook is an important source of information for many of us - also in the IT industry wherein multiple groups, we can reach out to job-seeker or person looking for a subcontractor. We often want to share information with people responsible for individual processes in the company, e.g. Human Resources. At its simplest case, we can send a link or screenshot – in the best case, we could send a link to the prepared record in the company’s CRM system.",[863,96680,96682],{"id":96681},"save-a-post-is-not-enough","“Save a post” is not enough",[842,96684,96685],{},"Copying interesting data takes little time, however, when we repeat that process often, it turns out to be time-consuming, for that it’s easy to make a mistake. What if that process would be reduced to two or three clicks using a simple extension? Therefore, we created the extension for Chrome browser that could copy the interesting data to the company's CRM system. We want to increase our productivity and quality of the data that we process every day, that’s why we got the idea to reduce function occupying several minutes to a few clicks. The extension was affectionately called “Scratch Me”. The simple extension works on the principle of adding a button to each displayed post on any selected Facebook group. After clicking the button, it gets data from post and displays a form containing: author of a post, the content, link, and date. If the data is correct, we can send it data to the CRM system.",[863,96687,96689],{"id":96688},"why-the-plugin","Why the plugin?",[842,96691,96692,96693,96697],{},"We have already created a few plugins that help our clients improve processes in their companies. However, the main reasons were the low entry threshold, limited access to data in closed groups as well as willingness to share our knowledge outside. The process of extension divided into two stages. The first stage of \"Proof of concept\" is a very simple plugin, that adds a button to each post and then extracts the data and displays as JSON format. The second stage, on which we are working, will include integration into the Copper CRM System (which we use every day in our company). Before we started writing chrome extension, it necessary was to know the latest Google Chrome documentation (",[846,96694,96695],{"href":96695,"rel":96696},"https://developer.chrome.com/extensions",[850],"). Owing to the fact that we used publicly accessible knowledge, we decided that the extension would be available in open source code.\nBased on preliminary tests and opinions, we see that the first extension version has the potential to develop. The next step will be integration with e.g. CRM SalesForce. You can follow the plugin development on GitHub and you are welcome to share your feedback.",[842,96699,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":96701},[96702,96703,96704],{"id":96674,"depth":729,"text":96675},{"id":96681,"depth":729,"text":96682},{"id":96688,"depth":729,"text":96689},"2019-11-14T00:00:00.000Z",{"src":96707},"/images/blog/musictechlab_blog_scratch-me-a-simple-chrome-extension-which-will-increase-your-productivity.webp",{"enabled":738,"items":96709},[96710,96712,96714],{"text":96711,"icon":2939},"Scratch Me reduces Facebook lead capture from several minutes of copying to 2-3 clicks.",{"text":96713,"icon":87068},"The Chrome extension extracts post author, content, link, and date directly into a CRM-ready form.",{"text":96715,"icon":50270},"Low entry threshold and closed Facebook group limitations made a browser plugin the ideal approach.",{},{"title":610,"description":94932},[18784],"bf4oyf0FJfgSiImS0368MimR8WDOwLPVZjjaXvTnAKA",{"id":96721,"title":350,"authors":96722,"badge":723,"body":96725,"category":756,"client":723,"date":96816,"description":96817,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":96818,"keyTakeaways":96820,"meta":96830,"navigation":738,"path":351,"seo":96831,"status":723,"stem":352,"tags":96832,"teaser":723,"__hash__":96833},"posts/blog/software-development/brave-3-0-how-we-conducted-website-redesign-part-2-solution.md",[96723],{"name":96487,"avatar":96724},{"src":96489},{"type":725,"value":96726,"toc":96811},[96727,96729,96737,96743,96747,96761,96765,96782,96786,96789,96806,96809],[842,96728,40867],{},[842,96730,96731,96732,1589,96734,96736],{},"Bravelab is a software developmeny company from Krakow, Poland specializing in ",[964,96733,34475],{},[964,96735,89658],{},". If you run such a business, you know that website redesign done by the company itself can be** **time-consuming. Our brave enough team, though, not capable of inventing a time expansion machine, get down to work.",[842,96738,96739,96740,861],{},"Our brave enough team, though, not capable of inventing a time expansion machine, get down to work. We had 4 people that were doing their regular job in the meantime. Bravelab does not do user experience per se, but with clever minds, the user experience process went on. Our lifeline was: ",[996,96741,96742],{},"1 month",[863,96744,96746],{"id":96745},"creating-personas","Creating personas",[842,96748,96749,96750,86269,96753,96756,96757,96760],{},"We asked ourselves questions: who are we going to talk to and in a what way. The team created a few personas: for a ",[996,96751,96752],{},"target client",[996,96754,96755],{},"co-worker",", and even a ",[996,96758,96759],{},"bad client",". As the work progressed, we personalized such pages as the contact page. User as we see it communicates with a website; we wanted to treat the contact page as if was a consultant whose role is to help the client.",[863,96762,96764],{"id":96763},"voice-of-tone","Voice of tone",[842,96766,96767,96768,96771,96772,96777,96778,96781],{},"Creating personas helped us shape words and communication. At first, content landed in ",[996,96769,96770],{},"low-fidelity"," wireframes (we used ",[846,96773,96776],{"href":96774,"rel":96775},"https://moqups.com/",[850],"moqups.com","). We learned that it is easier to discuss the content in a ",[996,96779,96780],{},"Google Document",". One document helped us be more consistent with wordings. The document also saved us time–it is easier to update wireframes after content is frozen; We designed the content in a conversational style–as if the user was talking to a representative of our company. At this stage, we did a lot of research on how other software companies communicate. We documented our findings and created guidelines. Again, paper and a pen turned out to be a set of tools that worked most before heading to a digital tools. Mind maps and the digital version–MindMup helped us in the brainstorming part.",[863,96783,96785],{"id":96784},"wireframes","Wireframes",[842,96787,96788],{},"From paper wireframes, low-fidelity digital ones, and more fine-tuned versions, we saw the progress. At different stages, we asked co-workers and partners for feedback. Others’ opinion helped us improve inconsistencies. Our crew created wireframes for key pages:",[958,96790,96791,96794,96797,96800,96803],{},[961,96792,96793],{},"Landing page",[961,96795,96796],{},"Career",[961,96798,96799],{},"Services",[961,96801,96802],{},"Case studies",[961,96804,96805],{},"Contact page.",[842,96807,96808],{},"We didn’t design all views, nor mobile versions–instead, we let the design agency apply our guidelines.",[842,96810,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":96812},[96813,96814,96815],{"id":96745,"depth":729,"text":96746},{"id":96763,"depth":729,"text":96764},{"id":96784,"depth":729,"text":96785},"2019-11-13T00:00:00.000Z","The process of redesigning a commercial website of the custom software development company. The process was conducted by the internal team.",{"src":96819},"/images/blog/musictechlab_blog_brave-3-0-how-we-conducted-website-redesign-part-2-solution.webp",{"enabled":738,"items":96821},[96822,96824,96826,96828],{"text":96823,"icon":4845},"A 4-person team completed the redesign process within a 1-month timeline.",{"text":96825,"icon":85476},"Content was drafted in Google Docs first, then applied to wireframes for consistency.",{"text":96827,"icon":72284},"Creating personas for target clients, co-workers, and bad clients shaped communication.",{"text":96829,"icon":5507},"Paper sketches and mind maps preceded digital tools at every brainstorming stage.",{},{"title":350,"description":96817},[18784],"s1CNlmb6OB9xQ9cNB2wJr4ZHM0WjegTz-bZdoQ-X0LA",{"id":96835,"title":358,"authors":96836,"badge":723,"body":96839,"category":756,"client":723,"date":96816,"description":96904,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":96905,"keyTakeaways":96907,"meta":96917,"navigation":738,"path":359,"seo":96918,"status":723,"stem":360,"tags":96919,"teaser":723,"__hash__":96920},"posts/blog/software-development/brave-3-0-redesign-process-part-1-challenge.md",[96837],{"name":96487,"avatar":96838},{"src":96489},{"type":725,"value":96840,"toc":96900},[96841,96843,96847,96850,96864,96867,96871,96891,96898],[842,96842,40867],{},[863,96844,96846],{"id":96845},"what-are-the-steps-of-redesigning-process","What are the steps of redesigning process?",[842,96848,96849],{},"The process consisted of a few phases:",[958,96851,96852,96855,96858,96861],{},[961,96853,96854],{},"Research–where we are and who we are",[961,96856,96857],{},"Creating personas–deciding who are we communicating with",[961,96859,96860],{},"Shaping communication by establishing a tone of voice",[961,96862,96863],{},"Creating wireframes",[842,96865,96866],{},"In the end, we had all the necessary information to hire a design agency that created a new design for us.",[863,96868,96869],{"id":52034},[996,96870,52035],{},[842,96872,96873,96874,1589,96876,96878,96879,96882,96883,96886,96887,96890],{},"Recently we have noticed a drop in requests for target technologies–",[964,96875,34475],{},[964,96877,89658],{},". As the site was not updated for a long time, our first idea was to update the visuals. The content was not complete. The page did not tell things that we care about: ",[996,96880,96881],{},"values",", how we work, and what we do. One can tell that we embodied ",[964,96884,96885],{},"The cobbler’s children"," way of life. Sad, but true. Soon, we realized that the new design will be done last. We started to dig deeper. Our goal was to ",[996,96888,96889],{},"transform the company"," into a focused software house from Krakow, Poland. The team responsible for the project analyzed numerous marketing and sales documents. We collected feedback from co-workers and target clients. Over 20 of them gave us feedback at different stages of the process. Their invaluable opinion helped us spot inconsistencies. To sum up, the research phase was the foundation for further steps.",[842,96892,96893,96897],{},[846,96894,96896],{"href":96895},"/blog/brave-3-0-how-we-conducted-website-redesign-part-2-solution/","In part two"," we're sharing insight into our approach. You will learn how creating personas, voice of tone, and wireframes can smooth out the process of rebranding.",[842,96899,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":96901},[96902,96903],{"id":96845,"depth":729,"text":96846},{"id":52034,"depth":729,"text":52035},"The first steps of our website redesign process. Why conducting in-depth research before designing UX/UI is the most important phase.",{"src":96906},"/images/blog/musictechlab_blog_brave-3-0-redesign-process-part-1-challenge.webp",{"enabled":738,"items":96908},[96909,96911,96913,96915],{"text":96910,"icon":4845},"Research phase was the foundation; over 20 people gave feedback at different stages.",{"text":96912,"icon":3847},"A drop in target technology requests triggered the need for a full rebrand.",{"text":96914,"icon":85476},"The website lacked key content: values, how we work, and what we do.",{"text":96916,"icon":50270},"New design came last; strategy and research had to come first.",{},{"title":358,"description":96904},[18784],"OjWFAaXjta4ilhgjtNqJnPt3Z3KP-0WlGvnzgBXHNes",{"id":96922,"title":362,"authors":96923,"badge":723,"body":96926,"category":756,"client":723,"date":96816,"description":96972,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":723,"keyTakeaways":96973,"meta":96981,"navigation":738,"path":363,"seo":96982,"status":723,"stem":364,"tags":96983,"teaser":723,"__hash__":96984},"posts/blog/software-development/brave-3-0-redesign-process-part-3-lesson-learned.md",[96924],{"name":96487,"avatar":96925},{"src":96489},{"type":725,"value":96927,"toc":96969},[96928,96945,96948,96951,96955,96967],[842,96929,96930,96931,96935,96936,96940,96941,96944],{},"We learned tons during the process of redesigning our site. In case you miss the previous parts, ",[846,96932,96934],{"href":96933},"/blog/brave-3-0-redesign-process-part-1-challenge","in the introduction"," we talked about a challenge Bravelab faced. ",[846,96937,96939],{"href":96938},"/blog/brave-3-0-how-we-conducted-website-redesign-part-2-solution","Part two ","describes our design process. The most important part was the research. To differentiate from other software companies we spent dozen of hours getting through materials, feedbacks and looking at how our competitors present. To be honest: we didn't know their goals, ambitions, and data behind their designs. We knew ours! Bravelab is a software house, not a design agency. Yet, we were ",[964,96942,96943],{},"bravenough"," to embark on brand transformation with what we know and what we have.",[842,96946,96947],{},"\"A Ship in Harbor Is Safe, But that Is Not What Ships Are Built For\"",[842,96949,96950],{},"Our new website is the output of a new strategy that we can test and improve in the next iterations. The project is living and Bravelab transformation will expose itself through digital media.",[863,96952,96954],{"id":96953},"more-in-the-series","More in the series:",[958,96956,96957,96962],{},[961,96958,96959],{},[846,96960,96961],{"href":96933},"Part 1–Challenge",[961,96963,96964],{},[846,96965,96966],{"href":96938},"Part 2–Solution",[842,96968,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":96970},[96971],{"id":96953,"depth":729,"text":96954},"Key lessons from our website redesign process. The most important factors to consider when conducting a redesign with an internal team.",{"enabled":738,"items":96974},[96975,96977,96979],{"text":96976,"icon":50270},"Research was the most important phase of the entire redesign process.",{"text":96978,"icon":3271},"Dozens of hours were spent analyzing competitors and collecting internal feedback.",{"text":96980,"icon":37696},"The new website is a testable output of a new strategy, not a final product.",{},{"title":362,"description":96972},[18784],"cEfXwnbLbCXgljUJV45SYyX8YjFU4MgAfQuAqK84QXg",{"id":96986,"title":450,"authors":96987,"badge":723,"body":96992,"category":756,"client":723,"date":97306,"description":97307,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":97308,"keyTakeaways":97310,"meta":97318,"navigation":738,"path":451,"seo":97319,"status":723,"stem":452,"tags":97320,"teaser":723,"__hash__":97321},"posts/blog/software-development/does-zappa-make-it-super-easy.md",[96988],{"name":96989,"avatar":96990},"Jarosław Szymla",{"src":96991},"/images/people/jaroslaw-szymla.webp",{"type":725,"value":96993,"toc":97293},[96994,96996,96999,97001,97004,97007,97010,97013,97016,97019,97023,97026,97032,97035,97038,97041,97043,97047,97050,97053,97056,97059,97062,97070,97073,97076,97079,97096,97099,97103,97106,97110,97118,97121,97125,97128,97145,97148,97151,97154,97157,97160,97163,97166,97169,97172,97175,97178,97181,97184,97192,97206,97209,97212,97215,97218,97222,97225,97230,97237,97240,97245,97255,97258,97262,97275,97278,97281,97284,97288,97291],[842,96995,40867],{},[842,96997,96998],{},"In this article, I'm going to show that deployment of the \"Hello World\" flask application with Zappa framework is really easy. Follow the steps and see for yourself.",[863,97000,3084],{"id":3083},[842,97002,97003],{},"At the beginning, let’s assume that we have pure Python 3 installation. Firstly, let’s install virtualenv (zappa needs it), zappa and AWS Command Line Interface:",[842,97005,97006],{},"pip install virtualenv\npip install zappa\npip install awscli",[842,97008,97009],{},"Now, create dictionary and virtualenv for our project:",[842,97011,97012],{},"mkdir brave-zappa-tutorial\nvirtualenv brave-zappa",[842,97014,97015],{},"And activate the venv:",[842,97017,97018],{},"source brave-zappa/bin/activate",[863,97020,97022],{"id":97021},"flask-app","Flask App",[842,97024,97025],{},"It’s time to create our flask hello.py (minimal working example from flask tutorial):",[842,97027,97028,97029,97031],{},"from flask import Flask\napp = Flask(",[996,97030,4184],{},")\n@app.route('/')\ndef hello_world():\nreturn 'Hello, World!'",[842,97033,97034],{},"Let’s test the app and type in console:",[842,97036,97037],{},"export FLASK_APP=hello.py\nflask run",[842,97039,97040],{},"When you visit website localhost:5000 in your browser, you will see “Hello World” from our application.",[842,97042,93143],{},[1074,97044,97046],{"id":97045},"aws-deployment","AWS Deployment",[842,97048,97049],{},"Now, we need to take step to deploy app on AWS Lambda. Firstly, set up your AWS credentials, run",[842,97051,97052],{},"aws configure",[842,97054,97055],{},"and follow the terminal wizard. As a result of execution this script, files under ~/.aws will be created. The next thing to do is creation of “zappa_settings.json” file. Command:",[842,97057,97058],{},"zappa init",[842,97060,97061],{},"will create initial setup. If it is necessary, you can add additional entries to this file i.e:",[958,97063,97064,97067],{},[961,97065,97066],{},"aws_region",[961,97068,97069],{},"roles entries (if role problem occurs, you’ ll have to set “manage_roles” to false and set “role_name”, “role_arn” by yourself.",[842,97071,97072],{},"And that’s it! Project is ready to deploy. Just you need to run (you need proper rights to manage stack):",[842,97074,97075],{},"zappa deploy",[842,97077,97078],{},"And project will be packet and send to the default stage. Zappa provides many additional functions, i.e:",[958,97080,97081,97084,97087,97090,97093],{},[961,97082,97083],{},"update - updates project on the server",[961,97085,97086],{},"Rollback - rollbacks to a previous version",[961,97088,97089],{},"Undeploy - removes the API Gateway and Lambda function",[961,97091,97092],{},"Status - checks status of your deployment",[961,97094,97095],{},"Tail - shows logs",[842,97097,97098],{},"And many more! Everything you’ll find on the zappa’s github page.",[863,97100,97102],{"id":97101},"bonus-working-with-dynamodb","Bonus! Working with DynamoDB",[842,97104,97105],{},"The application described below is very simple, let’s make them more advanced and add support for DynamoDB. Let’s assume that we want to create table to store users of our service.",[1074,97107,97109],{"id":97108},"setup-local-dynamodb","Setup local DynamoDB",[842,97111,97112,97113,97117],{},"If you want to test the database locally, you have to download them from: ",[846,97114,97115],{"href":97115,"rel":97116},"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html",[850],"- A more detailed description is available in the this link.Unpack and run(jre 6.x or newer is required):",[842,97119,97120],{},"java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb",[1074,97122,97124],{"id":97123},"user-table-creation","User table creation",[842,97126,97127],{},"Now, let’s create basic table to keep the user data. We need to add:",[842,97129,97130,97131,68304,97134],{},"app.config",[1086,97132,97133],{},"'DYNAMO_TABLES'",[1086,97135,97136,97137,97140,97141,97144],{},"\ndict(\nTableName='users',\nKeySchema=",[1086,97138,97139],{},"dict(AttributeName='username', KeyType='HASH')",",\nAttributeDefinitions=",[1086,97142,97143],{},"dict(AttributeName='username', AttributeType='S')",",\nProvisionedThroughput=dict(ReadCapacityUnits=5,\nWriteCapacityUnits=5)\n)",[842,97146,97147],{},"To our flask app. Let’s stop in this step for the moment and discuss the above code:",[842,97149,97150],{},"TableName it simply place for name of the table",[842,97152,97153],{},"KeySchema specifies the attributes that make up the primary key for a table",[842,97155,97156],{},"index.Of course, here you can place more than one key, but one",[842,97158,97159],{},"(and only one)must be HUSH.",[842,97161,97162],{},"KeyType the role that the key attribute will assume (HASH for partition",[842,97164,97165],{},"RANGE for sort key).",[842,97167,97168],{},"AttributeDefinitions",[842,97170,97171],{},"attributes that describe the key schama for the table and index. AttributeType can be S (String), N (number) or B (binary).",[842,97173,97174],{},"ProvisionedThroughput represents the throughput setting for a tableReadCapacityUnits",[842,97176,97177],{},"The maximum number of strongly consistent reads consumed per second before DynamoDB returns a ThrottlingException.",[842,97179,97180],{},"WriteCapacityUnits",[842,97182,97183],{},"The maximum number of writes consumed per second before DynamoDB returns a ThrottlingException",[842,97185,97186,97187,97191],{},"Full description available here: ",[846,97188,97189],{"href":97189,"rel":97190},"https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html",[850]," If you want to use local database, let’s set:",[842,97193,97130,97194,97197,97198,97201,97202,97205],{},[1086,97195,97196],{},"'DYNAMO_ENABLE_LOCAL'"," = True\napp.config",[1086,97199,97200],{},"'DYNAMO_LOCAL_HOST'"," = 'localhost'\napp.config",[1086,97203,97204],{},"'DYNAMO_LOCAL_PORT'"," = 8000",[842,97207,97208],{},"To use the table shema, you have to add:",[842,97210,97211],{},"dynamo = Dynamo()\ndynamo.init_app(app)",[842,97213,97214],{},"To create table:",[842,97216,97217],{},"with app.app_context():\ndynamo.create_all()",[1074,97219,97221],{"id":97220},"additional-views","Additional views",[842,97223,97224],{},"We will create two simple views, that will take arguments from link and make some operations on the database:",[50359,97226,97228],{"id":97227},"create_user",[996,97229,97227],{},[842,97231,97232,97233,97236],{},"@app.route('/put_user')\ndef create_user():\nusername = request.args.get('username')\nfirst_name = request.args.get('first_name')\ndynamo.tables",[1086,97234,97235],{},"'users'",".put_item(Item={\n'username': username,\n'first_name': first_name,\n})\nreturn \"User created\"",[842,97238,97239],{},"put _item - creates new item or update old if exist.",[50359,97241,97243],{"id":97242},"get_user",[996,97244,97242],{},[842,97246,97247,97248,97250,97251,97254],{},"@app.route('/get_user')\ndef get_user():\nusername = request.args.get('username')\nresponse = dynamo.tables",[1086,97249,97235],{},".get_item(\nKey={\n'username': username,\n}\n)\ntry:\nitem = response",[1086,97252,97253],{},"'Item'","\nexcept KeyError:\nreturn \"Item not found\"\nreturn str(item)",[842,97256,97257],{},"get_Item - returns a set of attributes for the item with the given primary key. If there is no matching item, GetItem does not return any data and there will be no Item element in the response",[1074,97259,97261],{"id":97260},"test-and-deploy","Test and deploy",[842,97263,97264,97265,97269,97270,97274],{},"To test your app, run and visit",[846,97266,97267],{"href":97267,"rel":97268},"http://localhost:5000/put_user?username=john_doe&first_name=john",[850],"to create record: {username: john_doe, first_name:john} and",[846,97271,97273],{"href":97267,"rel":97272},[850],"http://localhost:5000/get_user?username=john_doe"," to get record from database",[842,97276,97277],{},"To update project on the AWS run:",[842,97279,97280],{},"zappa update",[842,97282,97283],{},"And voila! It works on the server!",[863,97285,97287],{"id":97286},"task-for-you","Task for you",[842,97289,97290],{},"Taking data from the request link isn’t user friendly, so if you want to practice your flask and programing skills you can add some forms to project.",[842,97292,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":97294},[97295,97296,97299,97305],{"id":3083,"depth":729,"text":3084},{"id":97021,"depth":729,"text":97022,"children":97297},[97298],{"id":97045,"depth":1112,"text":97046},{"id":97101,"depth":729,"text":97102,"children":97300},[97301,97302,97303,97304],{"id":97108,"depth":1112,"text":97109},{"id":97123,"depth":1112,"text":97124},{"id":97220,"depth":1112,"text":97221},{"id":97260,"depth":1112,"text":97261},{"id":97286,"depth":729,"text":97287},"2019-11-09T00:00:00.000Z","The article provides step-by-step instructions for the easy implementation of the application in Zappa framework based on Python.",{"src":97309},"/images/blog/musictechlab_blog_does-zappa-make-it-super-easy.webp",{"enabled":738,"items":97311},[97312,97314,97316],{"text":97313,"icon":37696},"Zappa deploys Python Flask apps to AWS Lambda with minimal configuration.",{"text":97315,"icon":2939},"A Hello World Flask app can be deployed in under 10 minutes.",{"text":97317,"icon":1723},"Requires virtualenv, Zappa, and AWS CLI as the only prerequisites.",{},{"title":450,"description":97307},[18784,15279],"PpnXIqFJFM3ULNy0b9AP8nNibiceHpgpNmzK4a-AuMg",{"id":97323,"title":602,"authors":97324,"badge":723,"body":97327,"category":756,"client":723,"date":97486,"description":97487,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":97488,"keyTakeaways":97490,"meta":97498,"navigation":738,"path":603,"seo":97499,"status":723,"stem":604,"tags":97500,"teaser":723,"__hash__":97501},"posts/blog/software-development/progressive-web-apps-a-new-way-of-creating-mobile-application.md",[97325],{"name":87508,"avatar":97326},{"src":87797},{"type":725,"value":97328,"toc":97478},[97329,97331,97334,97344,97348,97351,97355,97358,97362,97365,97369,97372,97379,97383,97386,97390,97393,97432,97435,97438,97442,97445,97457,97460,97464,97467,97470,97473,97475],[842,97330,40867],{},[842,97332,97333],{},"Progressive Web Apps combine features of both websites and mobile applications. Under the presentation layer, which looks like a mobile application, they work like a regular responsive website extended with several new functionalities. We can easily distinguish 3 main features of PWAs:",[958,97335,97336,97339,97341],{},[961,97337,97338],{},"Reliable",[961,97340,28310],{},[961,97342,97343],{},"Engaging",[842,97345,97346],{},[996,97347,97338],{},[842,97349,97350],{},"It means that app loads instantly and nevers shows so-called downasaur, even with no network connection. Also when we launch app from the user’s home screen with wifi or data transfer turned off, the app will handle this and will show us cached data from our previous session.",[842,97352,97353],{},[996,97354,28310],{},[842,97356,97357],{},"Being able to load without Internet connection means that we do not have to worry about infinite loaders and spinners. We get the access to cached data immediately without turning on data transfer or Wi-Fi on our mobile device, refreshing application and so on. It is just one click on icon at your home screen!",[842,97359,97360],{},[996,97361,97343],{},[842,97363,97364],{},"It is all about user experience. User can easily add PWA to his home screen, without the need of installing it from the app store. So the user has the impression of using a native mobile application that actually is just a website.",[863,97366,97368],{"id":97367},"progressive-web-apps-requirements","Progressive Web Apps requirements",[842,97370,97371],{},"Everything sounds awesome, but to create PWA we must meet certain requirements. Firstly, PWA should be safe so it must be secured by HTTPS protocol. As we know PWA is just a website on the mobile device so it has to be responsive. Otherwise, the appearance of our application will be terrible. Moreover, PWA has to work even when we are not connected to the Internet. It does not have to be all functional but it should has at least a screen indicating that there is no connection. And app must be also reactive. It means that the application should react quickly to user actions, e.g. switching screens.",[842,97373,97374,97375,97378],{},"​{ \"short_name\": \"\", \"name\": \"\", \"icons\": ",[1086,97376,97377],{}," { \"src\": \"\", \"sizes\": \"144x144\", \"type\": \"image/png\" }",", \"start_url\": \"\", \"background_color\": \"\", \"theme_color\": \"\", \"display\": \"fullscreen\" }​",[863,97380,97382],{"id":97381},"how-to-create-pwa","How to create PWA?",[842,97384,97385],{},"Basically, we must only add some files to our current website. Firstly, you need to add manifest.json file to the project. This file describes application by defining e.g. app’s name and icons in various size. It is a good practice to put this file into the root directory of your project.",[1074,97387,97389],{"id":97388},"example-manifestjson-file","Example manifest.json file",[842,97391,97392],{},"After the file has been created, we need to tell the browser where it is located by adding link tag in index.html file.",[1013,97394,97398],{"className":97395,"code":97396,"language":97397,"meta":728,"style":728},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Clink href=\"/manifest.json\" rel=\"manifest\">\n","html",[895,97399,97400],{"__ignoreMap":728},[1086,97401,97402,97404,97407,97410,97412,97414,97417,97419,97422,97424,97426,97428,97430],{"class":1088,"line":1089},[1086,97403,11164],{"class":1146},[1086,97405,97406],{"class":4109},"link",[1086,97408,97409],{"class":1155}," href",[1086,97411,1440],{"class":1146},[1086,97413,1159],{"class":1146},[1086,97415,97416],{"class":1096},"/manifest.json",[1086,97418,1159],{"class":1146},[1086,97420,97421],{"class":1155}," rel",[1086,97423,1440],{"class":1146},[1086,97425,1159],{"class":1146},[1086,97427,10737],{"class":1096},[1086,97429,1159],{"class":1146},[1086,97431,11170],{"class":1146},[842,97433,97434],{},"The second step is to add Service Worker, responsible for saving files in Cache Storage. When the file has been downloaded by the browser, the Service Worker saves it to the cache to use it in the future when user will not be connected to the network. We also need to create service-worker.js file which contains list of files that should be remember by user’s browser.",[842,97436,97437],{},"​if('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js') .then(function(registration) { console.log('Registration successful, scope is:', registration.scope); }) .catch(function(error) { console.log('Service worker registration failed, error:', error); }); }​",[1074,97439,97441],{"id":97440},"example-service-worker-file","Example Service Worker file",[842,97443,97444],{},"After the file has been created, we need to tell the browser where it is located so we connect it in index.html file.",[842,97446,97447,97448,97456],{},"​var FILES_TO_CACHE = ",[1086,97449,97450,97451,97455],{}," '.', '",[846,97452,97453],{"href":97453,"rel":97454},"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700",[850],"', 'images/still_life-1600_large_2x.webp', 'images/still_life-800_large_1x.webp', 'images/still_life_small.webp', 'images/still_life_medium.webp', 'index.html', 'pages/offline.html', 'pages/404.html'","; var staticCacheName = 'pages-cache-v1'; self.addEventListener('install', function(event) { console.log('Attempting to install service worker and cache static assets'); event.waitUntil( caches.open(staticCacheName) .then(function(cache) { return cache.addAll(FILES_TO_CACHE); }) ); });​",[842,97458,97459],{},"We can also customize our PWA by changing top bar color. It is realized by adding meta tag with value theme-color in property name. In addition, we can install Lighthouse plugin to test our PWA but it is not necessary.",[863,97461,97463],{"id":97462},"advantages-and-disadvantages","Advantages and disadvantages",[842,97465,97466],{},"I think that the whole concept of PWA is really futuristic. For us - programmers, common code-base is a big pros. By implementing one application we receive both website and mobile app. For publishers, the benefits of the Progressive Web App are updates. In order to launch a new version of the mobile application, you usually have to work hard, of course depending of the platform. In the case of PWA, the only thing we need to do is upload new files to the server. High portability is another benefit of a PWA and it does not change your packaging or deployment model unlike the strategies for native apps. Moreover, PWAs are always available on your home screen for easy run and return. For business, PWAs will probably be a source of savings, because we need only Front-end team to achieve website and mobile version of our app.",[842,97468,97469],{},"But PWAs are not fully ready yet to replace native applications, so developers can sleep peacefully without worrying about their future. The PWAs do not support all browsers, with the exception of the newer ones. This point is quite essential, since the Safari browser alone covers 50% of browser market for mobile devices. Unfortunately, not all devices support the entire software functionality. For example, a progressive app for Android devices has some support issues, and a progressive app for iOS devices does not support notifications and shortcut prompting on the home screen. In addition, the iOS devices cover about 50% of the mobile device market in the USA.",[842,97471,97472],{},"As I said before, PWAs concept is really futuristic and can bring a lot profits but it have to be more polished to succeed.",[842,97474,52316],{},[1680,97476,97477],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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);}",{"title":728,"searchDepth":729,"depth":729,"links":97479},[97480,97481,97485],{"id":97367,"depth":729,"text":97368},{"id":97381,"depth":729,"text":97382,"children":97482},[97483,97484],{"id":97388,"depth":1112,"text":97389},{"id":97440,"depth":1112,"text":97441},{"id":97462,"depth":729,"text":97463},"2019-10-29T00:00:00.000Z","Creating a PWA mobile application is an alternative to a traditional native app. By implementing one application, we receive both - a website and a mobile app.",{"src":97489},"/images/blog/musictechlab_blog_progressive-web-apps-a-new-way-of-creating-mobile-application.webp",{"enabled":738,"items":97491},[97492,97494,97496],{"text":97493,"icon":4855},"PWAs deliver a native app experience using just a manifest.json and a Service Worker.",{"text":97495,"icon":5504},"One codebase produces both a website and a mobile app, reducing development costs significantly.",{"text":97497,"icon":3847},"PWAs still lack full Safari support and iOS push notifications, limiting 50% of the mobile market.",{},{"title":602,"description":97487},[18784],"CP_I4NBP2ctZ_TyHVelBxeb1p3MoRVWSJ_rWGn0EOKY",{"id":97503,"title":318,"authors":723,"badge":723,"body":97504,"category":756,"client":723,"date":97576,"description":97577,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":97578,"keyTakeaways":97580,"meta":97590,"navigation":738,"path":319,"seo":97591,"status":723,"stem":320,"tags":97592,"teaser":723,"__hash__":97593},"posts/blog/software-development/5-steps-to-implement-an-effective-communication-strategy-in-outsourcing-software-development-project.md",{"type":725,"value":97505,"toc":97571},[97506,97508,97511,97515,97518,97522,97525,97542,97546,97549,97566,97569],[842,97507,40867],{},[842,97509,97510],{},"Many companies are deciding on outsourcing software development projects to the near- or offshore companies. To achieve expected results it is highly important to define your needs and keep up-to-date with the progress of the project. Therefore, the crucial element in successful outcomes is effective communication between the client and the vendor.",[863,97512,97514],{"id":97513},"the-growing-importance-of-the-outsourcing-software-projects","The growing importance of the outsourcing software projects",[842,97516,97517],{},"What is more, companies spending on IT outsourcing services are rapidly growing every year. In the years 2016-2019, the CAGR (compound annual growth rate) value for the ITO was 4.7% and in the upcoming years, it is forecasted to grow up to 5.1% by 2023. The growing popularity of choosing the software development outsourcing strategy is due to many positive effects resulting from the implementation of this approach. The key drivers are costs reduction, exponential technologies, improving productivity, as well as gaining the strategic and competitive advantages.",[863,97519,97521],{"id":97520},"reasons-that-may-have-an-impact-on-effective-business-communication-in-outsourcing-projects","Reasons that may have an impact on effective business communication in outsourcing projects",[842,97523,97524],{},"As a highly dynamic and crucial component of a business strategy, selecting the best service provider to outsource software development operations bears substantial influence on successful outcomes. Nevertheless, completing the project and reaching the advantages is not possible when the relationship between the client and the vendor is not stable. Recent studies show that the main reasons that lead to the failure of the outsourcing projects are lack of communication and effective project coordination. There are 5 barriers to effective communication that occur the most often:",[958,97526,97527,97530,97533,97536,97539],{},[961,97528,97529],{},"Deferred replies",[961,97531,97532],{},"Deficiency of casual correspondence that is occasional and controlled",[961,97534,97535],{},"Non-recorded promises that are done amid videoconferencing or telephone conversation",[961,97537,97538],{},"Time zone",[961,97540,97541],{},"Cultural differences",[863,97543,97545],{"id":97544},"examples-of-good-communication-affecting-a-work-relationship","Examples of good communication affecting a work relationship",[842,97547,97548],{},"To establish effective communication the efforts should focus on involving the project development team, stakeholders, and end-users in order to achieve a greater understanding of needs. In addition, consider assessing project progress through the use of collaborative tools and graphics, which can reduce cultural and linguistic distances and bring project participants closer to the customer and supplier.",[958,97550,97551,97554,97557,97560,97563],{},[961,97552,97553],{},"Maximizing the presence of the stakeholders or/and end-users in the development team meetings to achieve greater compliance with the requirements, provide feedback, and resolve uncertainties in the project assumptions.",[961,97555,97556],{},"Holding at least one official meeting per week between stakeholders and the development team to resolve issues, verify progress, meet objectives, as well as agreements reached from previous sessions and respond to concerns.",[961,97558,97559],{},"Exchanging the messages that are concrete, coherent, complete, clear, and concise, whatever the means of communication is used.",[961,97561,97562],{},"Visualizing the workflow is one of the best options and ways to maintain effective communication. Therefore, it is recommended to use Kanban management so that both companies can visually observe which tasks are prepared, in progress, in the process of testing, or fully completed.",[961,97564,97565],{},"Using teamwork tools for communication between organizations (chats and joint emails), knowledge management (wikis, note management, photo management, a joint editable publication of documents and presentations), and project management (calendar control, monitoring of meetings, events, milestones control, overdue tasks, responsibility, and work distribution tools).",[842,97567,97568],{},"Implementing those five steps in your outsourcing strategy may be very helpful in establishing a stable and prosperous relationship with your vendor. It will lead to successful cooperation and satisfactional outcomes of the software development project.",[842,97570,52316],{},{"title":728,"searchDepth":729,"depth":729,"links":97572},[97573,97574,97575],{"id":97513,"depth":729,"text":97514},{"id":97520,"depth":729,"text":97521},{"id":97544,"depth":729,"text":97545},"2019-09-17T00:00:00.000Z","The crucial element in successful outcomes is effective communication between the client and the vendor. See why",{"src":97579},"/images/blog/musictechlab_blog_5-steps-communication.webp",{"enabled":738,"items":97581},[97582,97584,97586,97588],{"text":97583,"icon":72284},"Lack of communication is the top reason outsourcing projects fail.",{"text":97585,"icon":4845},"Hold at least one official meeting per week between stakeholders and the dev team.",{"text":97587,"icon":85476},"Visualize workflow with Kanban so both sides track progress in real time.",{"text":97589,"icon":3920},"IT outsourcing spending grew at 4.7% CAGR from 2016 to 2019.",{},{"title":318,"description":97577},[74615],"bETPdk5apPJrSf8tP1-J3Z2VV9mGfa8zqod8ks1b8Nw",{"id":97595,"title":590,"authors":97596,"badge":723,"body":97599,"category":756,"client":723,"date":97734,"description":97735,"extension":734,"faq":723,"featured":69,"featuredOrder":723,"hidden":738,"image":97736,"keyTakeaways":97738,"meta":97746,"navigation":738,"path":591,"seo":97747,"status":723,"stem":592,"tags":97748,"teaser":723,"__hash__":97749},"posts/blog/software-development/only-a-few-books-but-dozens-of-ideas.md",[97597],{"name":834,"to":720,"avatar":97598},{"src":722},{"type":725,"value":97600,"toc":97731},[97601,97603,97606,97609,97616,97622,97625,97663,97666,97679,97684],[842,97602,40867],{},[842,97604,97605],{},"When I read books I always highlight important ideas, cities, keywords and sentences. Last six books which I’ve read were particularly rich with words such as: focus, routine, procrastination, visualisation, courage, consciousness, etc. Of course those books contain a lot of brilliant thoughts but how can we remember everything and which of them could helps us to understand the world and make the difference?",[842,97607,97608],{},"One day I decided that I would read Bill Gates’ list of the most important books which I’ve found on LinkedIn. We know that Bill Gates reads tons of books every year so it was easier for me to start reading with pre-selected knowledge; knowledge about different ideas such as communication, leading, self-development, business, management, etc. Please don’t assume that I’m Bill Gates fanatic. I’m only a humble Linux fan but Bill Gates gives us examples of what is truly important when it comes to nowadays world’s problems.",[842,97610,97611,97612,97615],{},"My first goal (during reading) was catching and implementing some interesting ideas into my real life (family, work, self-development). Now, when I’m reading next book I’ve decided that I’ll be writing down my conclusions . It won’t be another review of the books. It will be more about how much effort I make to ",[964,97613,97614],{},"“get these ideas done”",". I want to find out if my results reflect theories from books.",[863,97617,97619],{"id":97618},"book-1-getting-things-done-by-david-allen",[996,97620,97621],{},"Book 1: Getting Things Done by David Allen",[842,97623,97624],{},"In my case one of the most important things when it comes to time-management is how to avoid multitasking and Believe me, it’s not easy to implement. Dozens of task management systems, hundreds of tools (from paper calendar to specialized online tools) not necessarily help to solve this problem. Additionally, as a CEO I have a dozen of things with different levels of importance (I know how to delegate tasks but there are still a lot of things to do). During the searching for the best time-management solution, I’ve found a book *Getting Things Done. *This is one of the most important books which I’ve discovered. David Allen gives us a few simple rules and shows a different perspective about time and planning. In a few words: linear planning doesn’t work! Focus on the most important things and put them into named boxes:",[958,97626,97627,97633,97639,97645,97651,97657],{},[961,97628,97629,97632],{},[996,97630,97631],{},"Inbox"," - everything that requires more than 2 minutes of work",[961,97634,97635,97638],{},[996,97636,97637],{},"Today"," - what should be done today (important for today!)",[961,97640,97641,97644],{},[996,97642,97643],{},"Later"," - things which are not necessarily important (and can wait some time)",[961,97646,97647,97650],{},[996,97648,97649],{},"Someday"," - ideas not to be forgotten",[961,97652,97653,97656],{},[996,97654,97655],{},"Completed"," - every completed task",[961,97658,97659,97662],{},[996,97660,97661],{},"Waiting"," - things which we don’t have influence on or we’re waiting for someone or something",[842,97664,97665],{},"That’s all! Simple as can be. Everything which requires less than two minutes of work needs to be already done.",[842,97667,97668,97669,97672,97673,97675,97676,97678],{},"After one year of using this schema I completed more than 2 500 task/goals - from phone calls to flat renovating (I know that because I use Trello board). Finally ideas from GTD methodology allow me to focus on ",[996,97670,97671],{},"today"," and what I need (want) to do ",[996,97674,97671],{},". The future is quite unpredictable (the further, the more unpredictable it gets) so that’s why ",[996,97677,97671],{}," is very important. I recommend this book to everyone who wants to develop self-time management skills. Of course it’s not perfect but I treat this as an entry point for all incoming tasks. I can decide why, what and when I want to do it. This is a powerful tool for better self-organisation.",[842,97680,97681],{},[996,97682,97683],{},"See you soon!",[958,97685,97686,97689,97692,97695,97698,97701,97704,97707,97710,97713,97716,97719,97722,97725,97728],{},[961,97687,97688],{},"Book 2: The 7 Habits of Highly Effective People Stephen R. Covey",[961,97690,97691],{},"Book 3: Essentialism by Greg McKeown",[961,97693,97694],{},"Book 4: Tribes by Seth Godin",[961,97696,97697],{},"Book 5: Managing the Mental Game by Jeff Boss",[961,97699,97700],{},"Book 6: The Dip by Seth Godin",[961,97702,97703],{},"Book 7: Start With Why by Simon Sinek",[961,97705,97706],{},"Book 8: The Go-Giver Leader by Bob Burg and John David Mann",[961,97708,97709],{},"Book 9: Freakonomics by Steven D. Levitt and Stephen J. Dubner",[961,97711,97712],{},"Book 10: Drive by Daniel H. Pink",[961,97714,97715],{},"Book 11: Give and Take by Adam Grant",[961,97717,97718],{},"Book 12: What They Don't Teach You at Harvard Business School by Mark H. McCormack",[961,97720,97721],{},"Book 13: Lean In by Sheryl Sandberg",[961,97723,97724],{},"Book 14: Daring Greatly by Brené Brown",[961,97726,97727],{},"Book 15: The War of Art by Steve Pressfield",[961,97729,97730],{},"Book 16: Quiet: The Power of Introverts by Susan Cain",{"title":728,"searchDepth":729,"depth":729,"links":97732},[97733],{"id":97618,"depth":729,"text":97621},"2018-11-09T00:00:00.000Z","Key takeaways from Bill Gates' recommended book list, including Getting Things Done, Deep Work, and practical ideas for time management and productivity.",{"src":97737},"/images/blog/musictechlab_blog_only-a-few-books-but-dozens-of-ideas.webp",{"enabled":738,"items":97739},[97740,97742,97744],{"text":97741,"icon":85476},"GTD methodology uses six simple boxes (Inbox, Today, Later, Someday, Completed, Waiting).",{"text":97743,"icon":3844},"Over 2,500 tasks completed in one year using the GTD system tracked via a Trello board.",{"text":97745,"icon":50270},"Linear planning fails; focusing on what matters today is the key to effective time management.",{},{"title":590,"description":97735},[74615],"arDOF3dkb_dprDthGlFfZHZWZPZn4Q4tSakkgF02YqU",{"id":97751,"title":97752,"body":723,"description":97753,"extension":97754,"meta":97755,"navigation":97756,"path":88297,"seo":97757,"stem":97758,"__hash__":97759},"blog/blog.yml","Insights & Articles","Stay informed with MusicTech Lab articles covering music technology trends, software development insights, startup news, and industry case studies.","yml",{},{"icon":78788},{"title":97752,"description":97753},"blog","OrJxnDRXVuVF7TAbZ0SfQx66Mr2i_XrsAyZU7uX5AP0",1777556298759]