[ZScript] DYME – Dynamic Youth Music Engine

Sprites, textures, sounds, code, and other resources belong here. Share and share-alike!
Forum rules
Before posting your Resource, please make sure you can answer YES to any of the following questions:
  • Is the resource ENTIRELY my own work?
  • If no to the previous one, do I have permission from the original author?
  • If no to the previous one, did I put a reasonable amount of work into the resource myself, such that the changes are noticeably different from the source that I could take credit for them?
If you answered no to all three, maybe you should consider taking your stuff somewhere other than the Resources forum.

Consult the Resource/Request Posting Guidelines for more information.

Please don't put requests here! They have their own forum --> here. Thank you!

[ZScript] DYME – Dynamic Youth Music Engine

Postby Gustavo6046 » Wed Oct 09, 2019 4:00 pm

This is a rather simple ZScript asset, that will aid you with your adaptive music endeavors. It supports horizontal mixing, and attenuation (assigning DYME to an anchor other than a player or global object). It also supports foreground effects, i.e. music effects that are played on top of a background music bar (in either the very beginning or very center), usually denoting specific situations or scenarios. It was inspired by the music mechanics in [url]Untitled Goose Game[/url] and The Legend of Zelda: Breath of the Wild (link to video that shows some of such mechanics in Breath of the Wild).

It even has a Node.js-esque event loop!

Currently, it's supposed to be initialized by writing some ZScript, and defining sounds to represent sections of music (usually 2 or 4 bars) in SNDINFO. Soon, the former part will be replaced by a DYMEINFO lump parser. For now, stay tuned.

Code: Select allExpand view
////////////////////////////////////////////////////////////////////////
//                                                                    //
//                '||'''|.    '\\  //`    '||\   /||`    '||''''|     //
// Dynamic         ||   ||      \\//       ||\\.//||      ||   .      //
// Youth           ||   ||       ||        ||     ||      ||'''|      //
// Music           ||   ||       ||        ||     ||      ||          //
// Engine         .||...|' ()   .||.   () .||     ||. () .||....| ()  //
//                                                                    //
// (c) 2019 Gustavo Ramos Rehermann. Available under the MIT License. //
//                                                                    //
////////////////////////////////////////////////////////////////////////



// A timeout. When it is done, it sends an event loop message
// to call the handler.
class Timeout extends Thinker {
    double              Duration;
    DYMEEventLoop       EventLoop;
    DYMEEventHandler    Handler;

    void Tick() {
        if (Duration >= 0) {
            Duration -= 1/35;

            if (Duration <= 0) {
                EventLoop.Message(Handler);
                Duration = -1;
            }
        }
    }

    static Timeout New(DYMEEventLoop EventLoop, DYMEEventHandler Handler, double Time) {
        Timeout Timer = Timeout(New("Timeout"));

        Timer.EventLoop = EventLoop;
        Timer.Handler = Handler;
        Timer.Duration = Time;

        return Timer;
    }
}


// A class to handle asynchronous events.
class DYMEEventLoop extends Thinker {
    Array<Timeout>      Timeouts;
    Array<DYMEMessage>  Queue;

    void Tick() {
        while (Queue.Size() > 0) {
            Queue[0].Process();
            Queue.Delete(0);
        }
    }

    void Message(DYMEEventHandler Handler) {
        let msg = DYMEMessage.New(Handler);

        Queue.Push(msg);
        msg.Registered();
    }

    uint SetTimeout(DYMEEventHandler Handler, double Time) {
        Timeouts.Push(Timeout.New(self, Handler, Time));
        return Timeouts.Size() - 1;
    }

    void ClearTimeout(uint Which) {
        if (Which < Timeouts.Size())
            Timeouts[Which].Duration = -1;
    }
}


// A message in the event loop.
class DYMEMessage extends Thinker {
    DYMEEventHandler    Handler;

    void Process() {
        Handler.Handle();
    }

    void Registered() {
        Handler.Registered();
    }

    static void New(DYMEEventHandler Handler) {
        DYMEMessage msg = DYMEMessage(New("DYMEMessage"));
        msg.Handler = Handler;

        return msg;
    }
}


// An event handler.
class DYMEEventHandler {
    virtual void Registered () {};
    virtual void Handle     () {};
}


// A background section of a song.
class DYMEBackSection extends Thinker {
    string          Sound;
    double          Duration;
    DYMEEventLoop   EventLoop;

    void Play(DYMEAnchor Anchor, DYMEEventHandler BackHandler = null, DYMEEventHandler FrontHandler = null, uint Attenuation = ATTN_None) {
        Anchor.A_PlaySound(Sound, Attenuation);

        if (FrontHandler)   EventLoop.SetTimeout(FrontHandler,  Duration / 2);
        if (BackHandler)    EventLoop.SetTimeout(BackHandler,   Duration);
        if (FrontHandler)   EventLoop.SetTimeout(FrontHandler,  Duration);
    }
}

// A foreground section of a song.
class DYMEFrontSection extends Thinker {
    string          Sound;
    double          Duration;

    void Play(DYMEAnchor Anchor, uint Attenuation = ATTN_None) {
        Anchor.A_PlaySound(Sound, Attenuation);
    }
}


// An actor to attach a DYME instance to.
class DYMEAnchor extends Actor {
    Actor AttachedTo;
    DYME Engine;

    Default {
        Radius 1;
        Height 1;
    }

    States {
        Spawn:
            TNT1 A -1;
            Stop;
    }

    static DYMEAnchor New(Vector3 Position) {
        let Anch = Spawn("DYMEAnchor", Position);
        return Anch;
    }

    static DYMEAnchor NewAttached(Actor To) {
        let Anch = Spawn("DYMEAnchor", To.pos);
        Anch.AttachedTo = To;

        return Anch;
    }

    void Tick() {
        SetXYZ(AttachedTo.pos);
    }
}


// A base trigger class.
class DYMETrigger extends Actor {
    Default {
        +SPECIAL;
    }

    void Touch(Actor Other) {
        DYMEAnchor anch;
        if ((anch = DYMEAnchor(Other)) == null) return;

        Trigger(anch.Engine);
    }

    virtual void Trigger(DYME Engine);
}


// A trigger class that pends to change the background track playing.
class DYMEBackgroundTrigger extends Actor {
    DYMETrack NewTrack;

    virtual void Trigger(DYME Engine) {
        Engine.NextTrack = NewTrack;
    }
}


// A trigger class that pends to play a new foreground section.
class DYMEForegroundTrigger extends Actor {
    DYMEFrontSection FrontSection;

    virtual void Trigger(DYME Engine) {
        Engine.NextFrontSection = FrontSection;
    }
}


// A special DYMEEventHandler subclass, common superclass of commonly used
// internal DYME handlers.
class DYMEEngineHandler extends DYMEEventHandler {
    DYME Engine;

    static New(DYME NEngine) {
        DYMEForegroundHandler FH = DYMEForegroundHandler(New("DYMEForegroundHandler"));
        Engine = NEngine;
    }
}

// And its subclasses...
class DYMEForegroundHandler extends DYMEEngineHandler {
    override void Handle() {
        Engine.Foreground();
    }
}

class DYMEBackgroundHandler extends DYMEEngineHandler {
    override void Handle() {
        Engine.NextSection();
    }
}

// A single track.
class DYMETrack {
    String                  Name;
    Array<DYMEBackSection>  Sections;

    static void New(String Name) {
        DYMETrack track = DYMETrack(New("DYMETrack"));
        track.Name = Name;

        return track;
    }

    void Add(DYMEBackSection Section) {
        Sections.Push(Section);
    }

    DYMEBackSection Random() {
        return Get(Random(0, Size() - 1));
    }

    DYMEBackSection Get(uint Index) {
        return Sections[Index];
    }

    uint Size() {
        return Sections.Size();
    }
}


//== The main engine class. ==//


class DYME extends Thinker {
    Array<DYMETrack>        Tracks;
   
    DYMETrack               Playing;
    DYMETrack               NextTrack;

    DYMEBackSection         PlayingSection;
    DYMEEventLoop           EventLoop;

    DYMEBackSection         NextBackSection;
    DYMEFrontSection        NextFrontSection;

    DYMEForegroundHandler   ForegroundHandler;
    DYMEBackgroundHandler   BackgroundHandler;



    // Called when it's time to switch to a new section (or track).
    void NextSection() {
        if (NextTrack != null) {
            NextFrontSection = null;
            NextSection = null;

            SetPlaying(NextTrack);
            NextTrack = null;
           
            return;
        }

        if (NextSection == null)
            SetPlayingSection(Playing.Random());

        else
            SetPlayingSection(NextSection);

        NextSection = Playing.Random();
    }
   

    // Changes the playing track. Playing MUST be null.
    void SetPlaying(DYMETrack Track) {
        Playing = Track;
        SetPlayingSection(Playing.Random());
    }


    // Changes the playing section.
    void SetPlayingSection(DYMEBackSection Section) {
        PlayingSection = Section;
        PlayingSection.Play();
    }


    // Loads the handlers.
    void PostBeginPlay() {
        BackgroundHandler = BackgroundHandler(new("DYMEBackgroundHandler"));
        ForegroundHandler = BackgroundHandler(new("DYMEForegroundHandler"));
    }

    // Adds a track.
    void Add(Track T, bool AutoPlay = true) {
        Tracks.Push(T);

        // Autoplays only if no track is currently playing.
        if (Playing == null && AutoPlay) {
            SetPlaying(T);
        }
    }
}
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [ZScript] DYME – Dynamic Youth Music Engine

Postby Cherno » Wed Oct 09, 2019 4:27 pm

A great idea! It would be perfect for my The Chaos Engine TC.
User avatar
Cherno
 
Joined: 06 Dec 2016

Re: [ZScript] DYME – Dynamic Youth Music Engine

Postby Nash » Wed Oct 09, 2019 5:55 pm

Got any runnable examples? I understand what it's supposed to do, but a usage example would help people really see the bigger picture.
User avatar
Nash
 
 
 
Joined: 27 Oct 2003
Location: Kuala Lumpur, Malaysia
Github ID: nashmuhandes

Re: [ZScript] DYME – Dynamic Youth Music Engine

Postby MFG38 » Thu Oct 10, 2019 9:39 am

Now this sounds cool! Coincidentally, I was just wondering if adaptive music would be doable in GZDoom - and apparently it is. :P
User avatar
MFG38
 
Joined: 14 Apr 2019
Location: Finland
Operating System: Windows 10/8.1/8 64-bit

Re: [ZScript] DYME – Dynamic Youth Music Engine

Postby Slax » Thu Oct 10, 2019 11:16 am

Dramatic Doom when?
User avatar
Slax
Saucy.
 
Joined: 19 Oct 2010
Location: Window office.

Re: [ZScript] DYME – Dynamic Youth Music Engine

Postby Gustavo6046 » Fri Oct 18, 2019 3:01 pm

This is a prototype, it has some syntax errors (I used extends by accident, reminiscent from UnrealScript and ZDCode 2. Whoops!)

I might make a runnable example soon, but I'd rather make Forester first and introduce DYME there instead.
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [ZScript] DYME – Dynamic Youth Music Engine

Postby XLightningStormL » Sat Oct 19, 2019 10:12 pm

Just make a runnable example with Manhunt music, be done with it lol
User avatar
XLightningStormL
DUSK Wiki Master
 
Joined: 09 May 2016
Discord: XLightningStormL#7461
Twitch ID: XLightmingStormL


Return to Resources

Who is online

Users browsing this forum: No registered users and 3 guests