package main import ( "fmt" "math" "math/rand" "strings" "time" "github.com/google/uuid" ) // Deterministic RNG — set once in main.go var rng *rand.Rand // InitRNG initializes the deterministic RNG. func InitRNG(seed int64) { rng = rand.New(rand.NewSource(seed)) } // ── UUID helpers ───────────────────────────────────────────────────────────── func newUUID() string { // Use rng bytes to make UUIDs deterministic var b [16]byte for i := range b { b[i] = byte(rng.Intn(256)) } b[6] = (b[6] & 0x0f) | 0x40 // version 4 b[8] = (b[8] & 0x3f) | 0x80 // variant 1 u, _ := uuid.FromBytes(b[:]) return u.String() } // ── Random primitives ──────────────────────────────────────────────────────── func randInt(min, max int) int { if min >= max { return min } return min + rng.Intn(max-min+1) } func randFloat(min, max float64) float64 { return min + rng.Float64()*(max-min) } func randBool() bool { return rng.Intn(2) == 0 } func randChance(pct int) bool { return rng.Intn(100) < pct } func pick[T any](slice []T) T { return slice[rng.Intn(len(slice))] } func pickN[T any](slice []T, n int) []T { if n >= len(slice) { cp := make([]T, len(slice)) copy(cp, slice) return cp } perm := rng.Perm(len(slice)) result := make([]T, n) for i := 0; i < n; i++ { result[i] = slice[perm[i]] } return result } func pickWeighted(weights []float64) int { total := 0.0 for _, w := range weights { total += w } r := rng.Float64() * total cum := 0.0 for i, w := range weights { cum += w if r <= cum { return i } } return len(weights) - 1 } // ── Power-law distribution ─────────────────────────────────────────────────── // PowerLaw returns a value following a power-law distribution. // Used for follower counts, play counts, etc. func PowerLaw(min, max int, alpha float64) int { u := rng.Float64() x := math.Pow( math.Pow(float64(max), alpha+1)-math.Pow(float64(min), alpha+1)*u+math.Pow(float64(min), alpha+1), 1.0/(alpha+1), ) v := int(x) if v < min { v = min } if v > max { v = max } return v } // ── Temporal helpers ───────────────────────────────────────────────────────── var baseTime = time.Date(2024, 9, 1, 0, 0, 0, 0, time.UTC) func seedTimeRange() (time.Time, time.Time) { return baseTime, time.Now() } // RandomTimeBetween returns a random time between start and end. func RandomTimeBetween(start, end time.Time) time.Time { delta := end.Sub(start) if delta <= 0 { return start } offset := time.Duration(rng.Int63n(int64(delta))) return start.Add(offset) } // RandomTimeAfter returns a random time between after and now. func RandomTimeAfter(after time.Time) time.Time { return RandomTimeBetween(after, time.Now()) } // RandomPastTime returns a random time within the seed period. func RandomPastTime() time.Time { start, end := seedTimeRange() return RandomTimeBetween(start, end) } // RandomRegistrationTime returns a time spread over 18 months with growth curve. // Earlier months have fewer registrations, later months have more. func RandomRegistrationTime(index, total int) time.Time { start, end := seedTimeRange() // Use a quadratic growth curve: more users registered recently t := float64(index) / float64(total) // Quadratic: t^1.5 gives accelerating growth adjusted := math.Pow(t, 1.5) offset := time.Duration(adjusted * float64(end.Sub(start))) return start.Add(offset) } // RealisticHour adjusts a time to a realistic hour (peak 18-23h, low 3-7h). func RealisticHour(t time.Time) time.Time { // Distribution weights for each hour (0-23) hourWeights := []float64{ 3, 2, 1.5, 1, 0.5, 0.5, 1, 2, // 00-07 4, 5, 6, 7, 7, 6, 6, 7, // 08-15 8, 10, 12, 14, 15, 13, 10, 6, // 16-23 } hour := pickWeighted(hourWeights) minute := rng.Intn(60) second := rng.Intn(60) return time.Date(t.Year(), t.Month(), t.Day(), hour, minute, second, 0, t.Location()) } // DaysAgo returns a time N days ago. func DaysAgo(d int) time.Time { return time.Now().Add(-time.Duration(d) * 24 * time.Hour) } // ── Artist names ───────────────────────────────────────────────────────────── var artistPrefixes = []string{ "DJ ", "MC ", "", "", "", "", "", "", "", "", // 80% no prefix } var artistFirstNames = []string{ "Luna", "Nova", "Ash", "Kai", "Rio", "Zara", "Milo", "Jade", "Axel", "Nina", "Felix", "Aria", "Leo", "Maya", "Theo", "Isla", "Remy", "Cleo", "Hugo", "Vera", "Soren", "Lyra", "Nico", "Freya", "Dante", "Suki", "Orion", "Yuna", "Blaze", "Kira", "Samir", "Elara", "Raven", "Zion", "Atlas", "Sage", "Phoenix", "Echo", "Jasper", "Aurora", } var artistLastParts = []string{ "beats", "sound", "wave", "bass", "pulse", "tone", "flux", "vibe", "sonic", "audio", "rhythm", "synth", "groove", "noise", "echo", "drift", "loop", "fade", "glitch", "static", } var stylizedNames = []string{ "KNTRL", "Nø Signal", "×void×", "fm.static", "lowkey", "BVSS", "h3lix", "wav.form", "neo.soul", "ctrl+alt", "404.wav", "null.set", "bit.crush", "sub.zero", "hi.pass", "vrtx", "DRMZ", "pxlgrid", "snthwv", "bassface", "deepstate", "skyline", "midnight", "goldchain", "velvet", "crimson", "phantom", "spectra", "zenith", "vertex", "cascade", "prism", "solstice", "tempest", "horizon", "meridian", } var frenchNames = []string{ "Amélie", "Baptiste", "Camille", "Dimitri", "Éloïse", "Florian", "Gaëlle", "Hugo", "Inès", "Jules", "Katia", "Léo", "Manon", "Nathan", "Océane", "Pierre", "Quentin", "Rose", "Sébastien", "Théo", "Ulysse", "Victoire", "William", "Xavier", "Yasmine", "Zoé", "Adrien", "Bérénice", "Clément", "Diane", "Émile", "Fantine", } var frenchLastNames = []string{ "Dubois", "Martin", "Lefèvre", "Moreau", "Laurent", "Garcia", "Petit", "Roux", "Bernard", "Robert", "Durand", "Simon", "Michel", "Richard", "Thomas", "Leroux", "David", "Bertrand", "Fournier", "Girard", "Mercier", "Dupont", "Lambert", "Bonnet", } // GenArtistName generates a realistic artist/stage name. func GenArtistName(index int) string { switch { case index%5 == 0: // Stylized name return pick(stylizedNames) case index%3 == 0: // French full name return pick(frenchNames) + " " + pick(frenchLastNames) default: // Prefix + first name + maybe suffix prefix := pick(artistPrefixes) name := pick(artistFirstNames) if randChance(30) { return prefix + name + " " + pick(artistLastParts) } return prefix + name } } // GenUsername generates a valid username (3-30 chars, [a-zA-Z0-9_]). func GenUsername(displayName string, index int) string { // Sanitize display name clean := strings.Map(func(r rune) rune { if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' { return r } if r == ' ' || r == '.' || r == '-' { return '_' } // Strip accents by dropping non-ASCII return -1 }, displayName) clean = strings.ToLower(clean) // Remove consecutive underscores for strings.Contains(clean, "__") { clean = strings.ReplaceAll(clean, "__", "_") } clean = strings.Trim(clean, "_") if len(clean) < 3 { clean = fmt.Sprintf("user_%d", index) } // Ensure uniqueness with index suffix if len(clean) > 20 { clean = clean[:20] } return fmt.Sprintf("%s_%04d", clean, index) } // ── Track titles ───────────────────────────────────────────────────────────── var trackAdjectives = []string{ "Neon", "Midnight", "Golden", "Velvet", "Crystal", "Electric", "Digital", "Cosmic", "Lunar", "Solar", "Deep", "Lost", "Broken", "Silent", "Frozen", "Burning", "Faded", "Hollow", "Phantom", "Sacred", "Vivid", "Crimson", "Amber", "Sapphire", "Endless", "Haunted", "Infinite", "Liquid", "Mystic", "Urban", "Ethereal", "Raw", } var trackNouns = []string{ "Dreams", "Waves", "Lights", "Rain", "Echo", "Pulse", "Horizon", "Shadow", "Storm", "Flame", "Drift", "Spiral", "Signal", "Memory", "Voyage", "Whisper", "Thunder", "Mirage", "Paradise", "Frequency", "Cascade", "Odyssey", "Nebula", "Prism", "Skyline", "Orbit", "Labyrinth", "Phoenix", "Tempest", "Solitude", "Reverie", "Zenith", } var trackTemplates = []string{ "%s %s", // "Neon Dreams" "%s %s", // "Midnight Waves" "The %s", // "The Storm" "%s", // "Pulse" "%s & %s", // "Lights & Shadows" "After %s", // "After Rain" "Last %s", // "Last Signal" "%s at Dawn", // "Waves at Dawn" "%s Protocol", // "Midnight Protocol" "%s Sessions", // "Deep Sessions" "Into the %s", // "Into the Storm" "%s Mode", // "Neon Mode" } // GenTrackTitle generates a realistic track title. func GenTrackTitle() string { tmpl := pick(trackTemplates) count := strings.Count(tmpl, "%s") switch count { case 0: return tmpl case 1: if randBool() { return fmt.Sprintf(tmpl, pick(trackAdjectives)) } return fmt.Sprintf(tmpl, pick(trackNouns)) case 2: return fmt.Sprintf(tmpl, pick(trackAdjectives), pick(trackNouns)) } return pick(trackAdjectives) + " " + pick(trackNouns) } // ── Albums ─────────────────────────────────────────────────────────────────── var albumTemplates = []string{ "%s EP", "%s Vol. %d", "%s Sessions", "The %s Collection", "%s", "%s Tapes", "%s Archives", "Side %s", } func GenAlbumTitle() string { tmpl := pick(albumTemplates) if strings.Contains(tmpl, "%d") { return fmt.Sprintf(tmpl, pick(trackNouns), randInt(1, 3)) } if strings.Contains(tmpl, "%s") { return fmt.Sprintf(tmpl, pick(trackAdjectives)) } return tmpl } // ── Playlist names ─────────────────────────────────────────────────────────── var playlistNames = []string{ "Chill Vibes 2025", "Workout Mix", "Late Night Sessions", "Morning Coffee", "Deep Focus", "Road Trip Playlist", "Party Starters", "Rainy Day Beats", "Sunset Grooves", "Study Sessions", "Pre-Game Hype", "Acoustic Chill", "Underground Gems", "Throwback Jams", "Midnight Drive", "Sunday Morning", "Bass Heavy", "Melodic Journey", "Lo-Fi & Chill", "Festival Favorites", "New Discoveries", "Indie Gems", "Electronic Essentials", "Hip-Hop Rotation", "Jazz Lounge", "Ambient Textures", "Funk & Soul", "Classical Focus", "Drum & Bass Energy", "Techno Temple", "House Classics", "Reggae Vibes", "Synthwave Dreams", "Post-Rock Journey", "Metal Mondays", "Blues Collection", "Downtempo Drift", "Peak Time Bangers", "Warm Up Set", "After Hours", "Bedroom Sessions", "Creative Flow", "Gym Power", "Cooking Tunes", "Cleaning Motivation", "Meditation Sounds", "Nature Ambiance", "City Sounds", "Vinyl Selections", "Hidden Treasures", "Fresh Finds Weekly", "Nostalgia Trip", } func GenPlaylistName(index int) string { if index < len(playlistNames) { return playlistNames[index] } adj := pick(trackAdjectives) noun := pick([]string{"Mix", "Playlist", "Collection", "Selection", "Vibes", "Beats", "Tunes", "Session"}) return fmt.Sprintf("%s %s #%d", adj, noun, index) } // ── Genres ──────────────────────────────────────────────────────────────────── type GenreInfo struct { Name string Slug string BPMRange [2]int Keys []string } var Genres = []GenreInfo{ {"Electronic", "electronic", [2]int{120, 140}, []string{"Am", "Cm", "Dm", "Em", "Fm"}}, {"House", "house", [2]int{118, 130}, []string{"Am", "Cm", "Dm", "G", "F"}}, {"Techno", "techno", [2]int{125, 150}, []string{"Am", "Dm", "Em", "Bm", "Cm"}}, {"Ambient", "ambient", [2]int{60, 100}, []string{"C", "D", "F", "G", "Am"}}, {"Drum & Bass", "drum-and-bass", [2]int{160, 180}, []string{"Am", "Dm", "Em", "Fm", "Gm"}}, {"Dubstep", "dubstep", [2]int{135, 145}, []string{"Dm", "Em", "Fm", "Gm", "Am"}}, {"Trance", "trance", [2]int{130, 150}, []string{"Am", "Dm", "Em", "Cm", "Fm"}}, {"Jazz", "jazz", [2]int{80, 160}, []string{"Dm", "Gm", "Cm", "Fm", "Bb"}}, {"Rock", "rock", [2]int{100, 160}, []string{"E", "A", "D", "G", "Em"}}, {"Pop", "pop", [2]int{90, 130}, []string{"C", "G", "Am", "F", "D"}}, {"Hip-Hop", "hip-hop", [2]int{70, 100}, []string{"Cm", "Fm", "Gm", "Dm", "Am"}}, {"Classical", "classical", [2]int{40, 180}, []string{"C", "D", "G", "Am", "Em"}}, {"Folk", "folk", [2]int{80, 130}, []string{"G", "C", "D", "Am", "Em"}}, {"Reggae", "reggae", [2]int{60, 90}, []string{"Am", "Dm", "G", "C", "Em"}}, {"Soul", "soul", [2]int{70, 110}, []string{"Dm", "Am", "Gm", "Cm", "Fm"}}, {"Funk", "funk", [2]int{90, 120}, []string{"Em", "Am", "Dm", "G", "A"}}, {"Blues", "blues", [2]int{60, 120}, []string{"E", "A", "B", "Am", "Em"}}, {"Metal", "metal", [2]int{100, 200}, []string{"Em", "Am", "Dm", "E", "D"}}, {"Indie", "indie", [2]int{90, 140}, []string{"C", "G", "Am", "F", "Em"}}, {"Experimental", "experimental", [2]int{40, 200}, []string{"", "Am", "C", "Dm", ""}}, {"World", "world", [2]int{70, 140}, []string{"Dm", "Am", "Em", "G", "C"}}, {"Latin", "latin", [2]int{80, 140}, []string{"Am", "Dm", "G", "C", "Em"}}, {"Lo-Fi", "lo-fi", [2]int{60, 90}, []string{"Am", "Dm", "Em", "C", "Fm"}}, } // GenreForArtist returns 1-3 consistent genres for an artist. func GenreForArtist(artistIndex int) []GenreInfo { // Use artist index as seed for consistency primary := artistIndex % len(Genres) count := 1 + rng.Intn(3) result := []GenreInfo{Genres[primary]} // Add nearby genres for i := 1; i < count && i < len(Genres); i++ { next := (primary + i) % len(Genres) result = append(result, Genres[next]) } return result } // ── Bio generator ──────────────────────────────────────────────────────────── var bioTemplates = []string{ "Producteur %s basé à %s. %s", "%s artist exploring the boundaries of %s and %s.", "Making %s since %d. %s", "Born in %s, raised on %s. Currently working on new material.", "%s producer & DJ. Resident at %s. %s", "Independent %s artist. Available for collaborations.", "Passionate about %s and sound design. %s", "Multi-instrumentalist blending %s with %s. Based in %s.", "Creating sonic landscapes between %s and %s.", "🎵 %s | %s | Based in %s", } var cities = []string{ "Paris", "Lyon", "Marseille", "Toulouse", "Bordeaux", "Nantes", "Lille", "Strasbourg", "Berlin", "London", "Amsterdam", "Barcelona", "Brussels", "Montreal", "New York", "Los Angeles", "Tokyo", "Lagos", "São Paulo", "Melbourne", "Stockholm", "Vienna", } var bioSuffixes = []string{ "Open for collabs.", "New EP coming soon.", "Bookings: see website.", "Label founder.", "Self-taught producer.", "Vinyl collector.", "Always looking for new sounds.", "Music is a journey, not a destination.", "", "", "", // Sometimes no suffix } func GenBio(genre string) string { tmpl := pick(bioTemplates) city := pick(cities) suffix := pick(bioSuffixes) year := randInt(2008, 2023) genre2 := pick(Genres).Name s := tmpl s = strings.Replace(s, "%s", genre, 1) s = strings.Replace(s, "%s", city, 1) s = strings.Replace(s, "%s", suffix, 1) s = strings.Replace(s, "%s", genre2, 1) s = strings.Replace(s, "%s", city, 1) s = strings.Replace(s, "%d", fmt.Sprintf("%d", year), 1) // Clean up leftover %s for strings.Contains(s, "%s") { s = strings.Replace(s, "%s", pick([]string{genre, city, suffix}), 1) } return s } // GenShortBio generates a 20-80 char bio for regular users. func GenShortBio() string { bios := []string{ "Music lover.", "Just here for the vibes.", "Discovering new sounds daily.", "Vinyl junkie.", "Festival goer. Playlist maker.", "Underground music enthusiast.", "Bass head.", "Chillwave addict.", "Lo-fi and coffee.", "Night owl.", "Always on repeat.", "Headphones on, world off.", "Eclectic listener.", "Beatmaker wannabe.", "Melody seeker.", "Groove collector.", "Sound explorer.", "Audio nerd.", "Rhythm is life.", "Music is the answer.", "Curator of good vibes.", } return pick(bios) } // ── Comment generator ──────────────────────────────────────────────────────── var commentTemplates = []string{ "This track is incredible! 🔥", "Love the vibe on this one.", "The production quality is amazing.", "Can't stop listening to this.", "This hits different at night.", "Perfect track for my playlist.", "The bass on this is insane.", "Beautiful melody, well done!", "Discovered this today, instant favorite.", "The mix is so clean.", "This deserves way more plays.", "Reminds me of early %s.", "Your best work yet!", "How did you get that synth sound?", "The drop at 1:30 is everything.", "Smooth production, respect.", "This needs to be in a movie soundtrack.", "Played this 10 times today already.", "The atmosphere on this track is unreal.", "Fire 🔥🔥🔥", "Adding this to every playlist.", "The outro is hauntingly beautiful.", "Chef's kiss on the mastering.", "This is what I needed today.", "Absolute banger.", "So underrated.", "The groove never stops.", "Your sound design skills are next level.", "Pure vibes.", "This brought me back to my first rave.", "Incredible arrangement.", "The percussion work is stellar.", "Love the vocal chops.", "This gives me goosebumps every time.", "What DAW did you use?", "Certified classic.", "Sharing this with everyone I know.", "The transition at 2:45 is genius.", "Perfect driving music.", "I could listen to this forever.", "The ambiance is just right.", } func GenComment() string { tmpl := pick(commentTemplates) if strings.Contains(tmpl, "%s") { return fmt.Sprintf(tmpl, pick(Genres).Name) } return tmpl } // ── Message generator ──────────────────────────────────────────────────────── var chatMessages = []string{ "Hey, what's up?", "Anyone heard the new release?", "Great set last night!", "Looking for collab partners.", "What plugins do you recommend?", "Just finished a new track, feedback welcome!", "Thanks for the follow!", "Love your latest upload.", "When's your next live stream?", "Check out my new playlist.", "The mix sounds great.", "What DAW are you using?", "Ableton or FL Studio?", "Anyone going to the festival this summer?", "Great community here.", "Need help with mixing, any tips?", "The platform is awesome.", "Just uploaded my first track!", "Who wants to do a remix swap?", "Your sound design is incredible.", "How do you get that bass sound?", "Thanks for sharing!", "Welcome to the group!", "Let's set up a listening session.", "Perfect track for the weekend.", "Anyone know good sample packs?", "Mastering tips?", "Studio session later?", "New beat ready for vocals.", "This community is fire.", "Appreciate the support everyone!", } func GenMessage() string { return pick(chatMessages) } // ── Product names ──────────────────────────────────────────────────────────── var productTypes = []string{"beat", "sample-pack", "beat", "sample-pack", "beat"} var productCategories = []string{ "beats", "samples", "electronic", "house", "acoustic", "stems", "hip-hop", "trap", "lo-fi", "ambient", "dj-tools", "sfx", "sync", } func GenProductTitle(genre string) string { templates := []string{ "%s Beat Pack Vol. %d", "%s Essentials", "%s Sample Collection", "%s Stems Bundle", "%s Loop Kit", "Premium %s Beats", "%s Sound Design Pack", "%s Producer Kit", "%s One-Shots", "%s Drum Kit", "%s Texture Pack", "%s Presets Collection", } tmpl := pick(templates) if strings.Contains(tmpl, "%d") { return fmt.Sprintf(tmpl, genre, randInt(1, 5)) } return fmt.Sprintf(tmpl, genre) } // ── IP and User-Agent generators ───────────────────────────────────────────── func GenIP() string { return fmt.Sprintf("%d.%d.%d.%d", randInt(1, 223), randInt(0, 255), randInt(0, 255), randInt(1, 254)) } var userAgents = []string{ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36 Chrome/119.0.0.0", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0", "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15", "Mozilla/5.0 (Android 14; Mobile) AppleWebKit/537.36 Chrome/120.0.0.0", "Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15", } func GenUserAgent() string { return pick(userAgents) } var countryCodes = []string{ "FR", "FR", "FR", "FR", "FR", // 50% French "US", "US", "DE", "GB", "BE", "CA", "CH", "ES", "IT", "NL", "JP", "BR", "AU", "SE", "PT", "MA", "SN", "CI", "TN", "DZ", } func GenCountry() string { return pick(countryCodes) } var sources = []string{"web", "web", "web", "mobile", "mobile", "api"} func GenSource() string { return pick(sources) } // ── Room names ─────────────────────────────────────────────────────────────── var roomNames = []string{ "General", "Production Tips", "Beat Marketplace", "Feedback Corner", "Mixing & Mastering", "Vinyl Talk", "Festival Chat", "Sample Swap", "Sound Design Lab", "Artist Lounge", "New Releases", "Collab Hub", "Genre Discussion", "Gear Talk", "Music Theory", "Business & Promo", "Open Mic", "Challenge of the Week", "Studio Setup", "Intro Thread", } // ── Gear ───────────────────────────────────────────────────────────────────── type GearTemplate struct { Name, Category, Brand, Model string Price float64 } var gearTemplates = []GearTemplate{ {"Ableton Push 3", "controller", "Ableton", "Push 3", 999}, {"Focal Shape 65", "monitors", "Focal", "Shape 65", 599}, {"RME Babyface Pro FS", "audio-interface", "RME", "Babyface Pro FS", 849}, {"Akai MPC One+", "sampler", "Akai", "MPC One+", 699}, {"Audio-Technica AT2020", "microphone", "Audio-Technica", "AT2020", 99}, {"Beyerdynamic DT 770 Pro", "headphones", "Beyerdynamic", "DT 770 Pro", 159}, {"Zoom H6", "recorder", "Zoom", "H6", 349}, {"Sennheiser MKH 416", "microphone", "Sennheiser", "MKH 416", 999}, {"Pioneer DDJ-1000", "dj-controller", "Pioneer", "DDJ-1000", 1199}, {"Allen & Heath Xone:96", "mixer", "Allen & Heath", "Xone:96", 1899}, {"Martin D-28", "guitar", "Martin", "D-28", 2999}, {"Neumann U87", "microphone", "Neumann", "U87", 3199}, {"Korg Minilogue XD", "synthesizer", "Korg", "Minilogue XD", 649}, {"Arturia KeyLab 61 MkII", "controller", "Arturia", "KeyLab 61 MkII", 449}, {"Universal Audio Apollo Twin", "audio-interface", "Universal Audio", "Apollo Twin", 899}, {"Yamaha HS8", "monitors", "Yamaha", "HS8", 349}, {"Shure SM7B", "microphone", "Shure", "SM7B", 399}, {"Roland TR-8S", "drum-machine", "Roland", "TR-8S", 599}, {"Moog Subsequent 37", "synthesizer", "Moog", "Subsequent 37", 1499}, {"Native Instruments Maschine+", "sampler", "Native Instruments", "Maschine+", 1399}, {"Audient iD14 MkII", "audio-interface", "Audient", "iD14 MkII", 249}, {"KRK Rokit 5 G4", "monitors", "KRK", "Rokit 5 G4", 179}, {"Rode NT1-A", "microphone", "Rode", "NT1-A", 229}, {"Elektron Digitakt II", "sampler", "Elektron", "Digitakt II", 899}, {"Teenage Engineering OP-1 Field", "synthesizer", "Teenage Engineering", "OP-1 Field", 1999}, } // ── Live stream titles ─────────────────────────────────────────────────────── var liveStreamTitles = []string{ "Friday Night Set", "Production Session — New EP Preview", "Beat Making LIVE", "Acoustic Session — Unplugged", "Late Night Vinyl Mix", "Sunday Jazz Session", "Studio Tour & Q&A", "Making a Track From Scratch", "Mixing Masterclass", "Sample Flipping Challenge", "Ambient Soundscape Session", "Open Decks Night", "Synth Jam Session", "Drum Programming Workshop", "Vocal Recording Session", "Lo-Fi Beats to Study To — LIVE", "Deep House Sunday", "Techno Warehouse Set", "Indie Acoustic Session", "Sound Design Exploration", }