Skip to content
Snippets Groups Projects

Ambient-Mixer Renderer

  • Clone with SSH
  • Clone with HTTPS
  • Embed
  • Share
    The snippet can be accessed without any authentication.
    Authored by Kevin Whitaker

    Python script to render downloaded Ambient Mixer data(using downloader from https://github.com/Philooz/pyambientmixer) to mp3.

    Edited
    snippetfile1.txt 4.30 KiB
    #!/usr/bin/env python
    """Render Ambient-Mix XML to MP3"""
    
    import untangle, pydub, argparse, math, os
    from typing import List
    
    __author__ = "Kevin Whitaker"
    __license__ = "GPL"
    __version__ = "1.0"
    
    human_unit_to_sec = {
            "1m": 60,
            "10m": 600,
            "1h": 3600
    }
    
    def convert_volume_to_decibel(volume: int):
        return -(math.log10(1+volume/100))
    
    def convert_balance_to_pan(balance: int):
        return (balance*2)/100
    
    def load_sample_to_segment(audio_file):
        if not os.path.exists(audio_file):
            print("Needed audio file "+audio_file+" does not exist.")
            exit(1)
        return pydub.AudioSegment.from_mp3(audio_file)
    
    class Channel():
        base_segment: pydub.AudioSegment
        length: int = 3600
        local_url: str = ""
        name: str = ""
        id: int = -1
        url: str = ""
        muted: bool = False
        volume: int = 100 #between 0 and 100
        balance: int = 0 #between -50 and +50
        random: bool = False
        random_counter: int = 1
        random_unit: int = human_unit_to_sec["1h"]
        crossfade: bool = False
        
        def __init__(self, length: int, source_dir: str, id: int):
            self.length = length
            self.id = id
            self.local_url = source_dir+"/"+str(id)+".mp3"
            self.base_segment = pydub.AudioSegment.silent(duration= length*1000)
        
        def render_channel(self):
            if not self.muted:
                inital_audio = load_sample_to_segment(self.local_url)
                gained_audio = inital_audio.apply_gain(convert_volume_to_decibel(self.volume))
                panned_audio = gained_audio.pan(convert_balance_to_pan(self.balance))
                if self.random:
                    trigger_every = int(self.random_unit/self.random_counter)
                    canvas: pydub.AudioSegment = self.base_segment
                    for sec in range(trigger_every,self.length,trigger_every):
                        canvas = canvas.overlay(panned_audio,position=sec*1000)
                    return canvas
                elif self.crossfade:
                    fadein_audio = panned_audio.fade_in(duration=1000)
                    crossfaded_audio = fadein_audio.fade_out(duration=1000)
                    return self.base_segment.overlay(crossfaded_audio, loop=True)
                else:
                    return self.base_segment.overlay(panned_audio, loop=True)
            else:
                return self.base_segment
    
    class Mix():
        channels: List[Channel] = []
        length: int = 3600
        final_segment: pydub.AudioSegment
        
        def __init__(self, xml_file: str, length: int, source_dir: str):
            self.length = length
            self.final_segment = pydub.AudioSegment.silent(duration=length*1000)
            xml = untangle.parse(xml_file)
            for channel_num in range(1,9):
                channel_xml = getattr(xml.audio_template, "channel"+str(channel_num))
                channel = Channel(length, source_dir, channel_xml.id_audio.cdata)
                channel.name = channel_xml.name_audio.cdata
                channel.url = channel_xml.url_audio.cdata
                channel.muted = channel_xml.mute.cdata == "true"
                channel.volume = int(channel_xml.volume.cdata)
                channel.balance = int(channel_xml.balance.cdata)
                channel.random = channel_xml.random.cdata == "true"
                channel.random_counter = int(channel_xml.random_counter.cdata)
                channel.random_unit = human_unit_to_sec[channel_xml.random_unit.cdata]
                channel.crossfade = channel_xml.crossfade.cdata == "true"
                self.channels.append(channel)
                
        def render_channels(self):
            for channel in self.channels:
                self.final_segment = self.final_segment.overlay(channel.render_channel())
        
        def export_final(self, outfile: str):
            self.final_segment.export(outfile, format="mp3")
    
    
    parser = argparse.ArgumentParser(description="Render Ambient-Mix XML to MP3")
    parser.add_argument("-l", help="length of final render in seconds", default=3600, dest="length")
    parser.add_argument("-s", help="directory containing audio samples", default="sounds", dest="samples")
    parser.add_argument("infile", help="Input XML file defining mix.")
    parser.add_argument("outfile", help="Output MP3 file of rendered mix.")
    args = parser.parse_args()
    
    if not os.path.exists(args.samples):
        print("Samples directory does not exist.")
        exit(1)
    
    mixer = Mix(args.infile, int(args.length), args.samples)
    mixer.render_channels()
    mixer.export_final(args.outfile)
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment