Extract monolithic seed main.go into separate files per domain: users, tracks, playlists, chat, analytics, marketplace, social, content, live, moderation, notifications, and misc. Add config, fake data helpers, and utility modules. Update Makefile targets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
636 lines
25 KiB
Go
636 lines
25 KiB
Go
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",
|
||
}
|