{"TotalCount":4,"Files":[{"Ident":"attackhelicopter.aitools","Path":"Editor/SpectogramWidget.cs","FileName":"SpectogramWidget.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":76432,"IsPrivate":false,"Code":"using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Threading.Tasks;\r\nusing System.IO;\r\nusing Editor;\r\nusing Sandbox;\r\nusing System.Text;\r\n\r\nclass SpectogramWidget : Widget \r\n{\r\n    private short[] samples;\r\n    private int sampleRate;\r\n    private List\u003Cint\u003E splitPoints = new List\u003Cint\u003E();\r\n    private int? dragPoint = null;\r\n    private Label loadingLabel;\r\n    private Label dropLabel;\r\n    private bool isLoading = true;\r\n    public SoundFile CurrentSound { get; private set; }\r\n\r\n    public SpectogramWidget(SoundFile soundFile) : base(null)\r\n    {\r\n        MinimumSize = 100;\r\n        MouseTracking = true;\r\n        AcceptDrops = true;\r\n\r\n        loadingLabel = new Label(this);\r\n        loadingLabel.Text = \u0022Loading audio data...\u0022;\r\n        loadingLabel.Visible = false;\r\n        \r\n        dropLabel = new Label(this);\r\n        dropLabel.Text = \u0022Drop a sound file here\u0022;\r\n        dropLabel.SetStyles(\u0022font-size: 18px; color: #aaa; text-align: center;\u0022);\r\n        \r\n        if (soundFile != null)\r\n        {\r\n            LoadSound(soundFile);\r\n        }\r\n    }\r\n\r\n    public async void LoadSound(SoundFile soundFile)\r\n    {\r\n        CurrentSound = soundFile;\r\n        isLoading = true;\r\n        samples = null;\r\n        splitPoints.Clear();\r\n        \r\n        loadingLabel.Visible = true;\r\n        dropLabel.Visible = false;\r\n\r\n        await LoadAudioDataAsync(soundFile);\r\n    }\r\n\r\n    private async Task LoadAudioDataAsync(SoundFile soundFile)\r\n    {\r\n        try \r\n        {\r\n            await soundFile.LoadAsync();\r\n            samples = await soundFile.GetSamplesAsync();\r\n            \r\n            if (samples == null)\r\n            {\r\n                loadingLabel.Text = \u0022Failed to load audio data\u0022;\r\n                return;\r\n            }\r\n\r\n            sampleRate = soundFile.Rate;\r\n            splitPoints.Add(0);\r\n            splitPoints.Add(samples.Length - 1);\r\n\r\n            loadingLabel.Visible = false;\r\n            isLoading = false;\r\n\r\n            Update();\r\n        }\r\n        catch (Exception ex)\r\n        {\r\n            loadingLabel.Text = $\u0022Error loading audio: {ex.Message}\u0022;\r\n        }\r\n    }\r\n\r\n    protected override void DoLayout()\r\n    {\r\n        base.DoLayout();\r\n\r\n        if (loadingLabel != null)\r\n        {\r\n            loadingLabel.Position = new Vector2(10, Height / 2 - 10);\r\n            loadingLabel.Size = new Vector2(Width - 20, 20);\r\n        }\r\n\r\n        if (dropLabel != null)\r\n        {\r\n            dropLabel.Position = new Vector2(10, Height / 2 - 10);\r\n            dropLabel.Size = new Vector2(Width - 20, 20);\r\n        }\r\n    }\r\n\r\n    public override void OnDragDrop(DragEvent e)\r\n    {\r\n        base.OnDragDrop(e);\r\n\r\n        if (!e.Data.HasFileOrFolder) return;\r\n\r\n        var asset = AssetSystem.FindByPath(e.Data.FileOrFolder);\r\n        if (asset?.AssetType != AssetType.SoundFile) return;\r\n\r\n        var soundFile = SoundFile.Load(asset.Path);\r\n        if (soundFile != null)\r\n        {\r\n            LoadSound(soundFile);\r\n        }\r\n    }\r\n\r\n    public override void OnDragHover(DragEvent e)\r\n    {\r\n        base.OnDragHover(e);\r\n\r\n        if (!e.Data.HasFileOrFolder) return;\r\n\r\n        var asset = AssetSystem.FindByPath(e.Data.FileOrFolder);\r\n        if (asset?.AssetType != AssetType.SoundFile) return;\r\n\r\n        e.Action = DropAction.Link;\r\n    }\r\n\r\n    protected override void OnMouseClick(MouseEvent e)\r\n    {\r\n        if (isLoading) return;\r\n        \r\n        base.OnMouseClick(e);\r\n\r\n        if (e.Button == MouseButtons.Left)\r\n        {\r\n            var samplePos = (int)(e.LocalPosition.x / Width * samples.Length);\r\n            var nearPoint = splitPoints.FirstOrDefault(p =\u003E Math.Abs(p - samplePos) \u003C (samples.Length / Width * 5));\r\n            \r\n            if (nearPoint != default)\r\n            {\r\n                dragPoint = splitPoints.IndexOf(nearPoint);\r\n            }\r\n            else\r\n            {\r\n                splitPoints.Add(samplePos);\r\n                splitPoints.Sort();\r\n                Update();\r\n            }\r\n        }\r\n    }\r\n\r\n    protected override void OnMouseMove(MouseEvent e)\r\n    {\r\n        if (isLoading) return;\r\n        \r\n        base.OnMouseMove(e);\r\n\r\n        if (dragPoint.HasValue)\r\n        {\r\n            var samplePos = (int)(e.LocalPosition.x / Width * samples.Length);\r\n            splitPoints[dragPoint.Value] = samplePos;\r\n            splitPoints.Sort();\r\n            Update();\r\n        }\r\n    }\r\n\r\n    protected override void OnMouseReleased(MouseEvent e)\r\n    {\r\n        if (isLoading) return;\r\n        \r\n        base.OnMouseReleased(e);\r\n        dragPoint = null;\r\n    }\r\n\r\n    protected override void OnPaint()\r\n    {\r\n        base.OnPaint();\r\n\r\n        if (isLoading || samples == null)\r\n        {\r\n            return;\r\n        }\r\n\r\n        Paint.ClearPen();\r\n        Paint.SetBrush(Theme.Grey.WithAlpha(0.1f));\r\n        Paint.DrawRect(LocalRect);\r\n\r\n        Paint.SetPen(Theme.Blue);\r\n        var samplesPerPixel = samples.Length / Width;\r\n        for (int x = 0; x \u003C Width; x\u002B\u002B)\r\n        {\r\n            var startSample = (int)(x * samplesPerPixel);\r\n            var endSample = Math.Min(startSample \u002B samplesPerPixel, samples.Length);\r\n            \r\n            var max = short.MinValue;\r\n            var min = short.MaxValue;\r\n            \r\n            for (int i = startSample; i \u003C endSample; i\u002B\u002B)\r\n            {\r\n                max = Math.Max(max, samples[i]);\r\n                min = Math.Min(min, samples[i]);\r\n            }\r\n\r\n            var y1 = Height / 2 \u002B (min / (float)short.MaxValue * Height / 2);\r\n            var y2 = Height / 2 \u002B (max / (float)short.MaxValue * Height / 2);\r\n            \r\n            Paint.DrawLine(new Vector2(x, y1), new Vector2(x, y2));\r\n        }\r\n\r\n        Paint.SetPen(Theme.Red);\r\n        foreach (var point in splitPoints)\r\n        {\r\n            var x = point / (float)samples.Length * Width;\r\n            Paint.DrawLine(new Vector2(x, 0), new Vector2(x, Height));\r\n        }\r\n    }\r\n\r\n    public List\u003Cint\u003E GetSplitPoints()\r\n    {\r\n        return new List\u003Cint\u003E(splitPoints);\r\n    }\r\n\r\n    public void SplitCurrentSound(Action\u003CSoundFile\u003E onSoundCreated)\r\n    {\r\n        if (CurrentSound == null || samples == null) return;\r\n\r\n        var splitPoints = GetSplitPoints();\r\n        if (splitPoints.Count \u003C 2) return;\r\n\r\n        try\r\n        {\r\n            var baseFileName = Path.GetFileNameWithoutExtension(CurrentSound.ResourcePath);\r\n            var outputDir = Path.Combine(\r\n                Project.Current.GetAssetsPath(),\r\n                \u0022generated\u0022,\r\n                $\u0022{baseFileName}_splits\u0022\r\n            );\r\n            Directory.CreateDirectory(outputDir);\r\n\r\n            for (int i = 0; i \u003C splitPoints.Count - 1; i\u002B\u002B)\r\n            {\r\n                var start = splitPoints[i];\r\n                var end = splitPoints[i \u002B 1];\r\n                var length = end - start;\r\n\r\n                var segmentSamples = new short[length];\r\n                Array.Copy(samples, start, segmentSamples, 0, length);\r\n\r\n                var wavPath = Path.Combine(outputDir, $\u0022{baseFileName}_part_{i \u002B 1}.wav\u0022);\r\n\r\n                using (var writer = new BinaryWriter(File.Create(wavPath)))\r\n                {\r\n                    writer.Write(Encoding.ASCII.GetBytes(\u0022RIFF\u0022));\r\n                    writer.Write(36 \u002B (segmentSamples.Length * 2));\r\n                    writer.Write(Encoding.ASCII.GetBytes(\u0022WAVE\u0022));\r\n\r\n                    writer.Write(Encoding.ASCII.GetBytes(\u0022fmt \u0022));\r\n                    writer.Write(16);\r\n                    writer.Write((short)1);\r\n                    writer.Write((short)CurrentSound.Channels);\r\n                    writer.Write(CurrentSound.Rate);\r\n                    writer.Write(CurrentSound.Rate * CurrentSound.Channels * 2);\r\n                    writer.Write((short)(CurrentSound.Channels * 2));\r\n                    writer.Write((short)16);\r\n\r\n                    writer.Write(Encoding.ASCII.GetBytes(\u0022data\u0022));\r\n                    writer.Write(segmentSamples.Length * 2);\r\n\r\n                    foreach (var sample in segmentSamples)\r\n                    {\r\n                        writer.Write(sample);\r\n                    }\r\n                }\r\n\r\n                var asset = AssetSystem.RegisterFile(wavPath);\r\n                if (asset != null)\r\n                {\r\n                    var soundFile = SoundFile.Load(asset.RelativePath);\r\n                    if (soundFile != null)\r\n                    {\r\n                        onSoundCreated?.Invoke(soundFile);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        catch (Exception ex)\r\n        {\r\n            Log.Error($\u0022Error splitting sound: {ex.Message}\u0022);\r\n        }\r\n    }\r\n}"},{"Ident":"attackhelicopter.aitools","Path":"Editor/SoundAddons.cs","FileName":"SoundAddons.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":76432,"IsPrivate":false,"Code":"using System;\r\nusing System.IO;\r\nusing System.Net.Http;\r\nusing System.Text;\r\nusing System.Text.Json;\r\nusing System.Reflection;\r\nusing Sandbox;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\n\r\nnamespace Editor;\r\n\r\npublic static class SoundAddons\r\n{\r\n\tprivate static string previousSoundGenerationPromp;\r\n\tprivate static string previousSoundGenerationDuration;\r\n\r\n\t// [ContextMenuFor( typeof( SoundFile ), \u0022Generate sound\u0022, \u0022create\u0022 )]\r\n\tpublic static void Generate( SoundEvent soundEvent, Action\u003CSoundFile\u003E finished )\r\n\t{\r\n\t\tstring defaultInput = !string.IsNullOrEmpty( previousSoundGenerationPromp )\r\n\t\t\t? previousSoundGenerationPromp\r\n\t\t\t: \u0022A dog barking\u0022;\r\n\r\n\t\tDialog.AskString( ( description ) =\u003E\r\n\t\t{\r\n\t\t\tpreviousSoundGenerationPromp = description;\r\n\r\n\t\t\tDialog.AskString( async ( durationStr ) =\u003E\r\n\t\t\t{\r\n\t\t\t\tpreviousSoundGenerationDuration = durationStr;\r\n\t\t\t\tif ( !float.TryParse( durationStr, out float duration ) || duration \u003C 0.5f || duration \u003E 22f )\r\n\t\t\t\t{\r\n\t\t\t\t\tLog.Error( \u0022Duration must be between 0.5 and 22 seconds\u0022 );\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\ttry\r\n\t\t\t\t{\r\n\t\t\t\t\tstring apiKey = SoundSettings.Settings.ElevenLabsApiKey;\r\n\t\t\t\t\tvar client = new HttpClient();\r\n\t\t\t\t\tclient.DefaultRequestHeaders.Add( \u0022xi-api-key\u0022, apiKey );\r\n\r\n\t\t\t\t\tvar content = new StringContent( JsonSerializer.Serialize( new\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ttext = description,\r\n\t\t\t\t\t\tduration_seconds = duration,\r\n\t\t\t\t\t\tprompt_influence = 0.3\r\n\t\t\t\t\t} ), Encoding.UTF8, \u0022application/json\u0022 );\r\n\r\n\t\t\t\t\tvar response = await client.PostAsync(\r\n\t\t\t\t\t\t\u0022https://api.elevenlabs.io/v1/sound-generation\u0022,\r\n\t\t\t\t\t\tcontent\r\n\t\t\t\t\t);\r\n\r\n\t\t\t\t\tif ( response.IsSuccessStatusCode )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar bytes = await response.Content.ReadAsByteArrayAsync();\r\n\r\n\t\t\t\t\t\tstring safeFileName = description.Replace( \u0022 \u0022, \u0022_\u0022 ).Replace( \u0022/\u0022, \u0022_\u0022 ).Replace( \u0022\\\\\u0022, \u0022_\u0022 );\r\n\t\t\t\t\t\tvar mp3Path = Path.Combine(\r\n\t\t\t\t\t\t\tSoundSettings.Settings.GenerationPath,\r\n\t\t\t\t\t\t\t$\u0022{safeFileName}_{DateTime.Now:yyyyMMddHHmmss}.mp3\u0022\r\n\t\t\t\t\t\t);\r\n\r\n\t\t\t\t\t\tvar fullMp3Path = Path.Combine( Project.Current.GetAssetsPath(), mp3Path );\r\n\t\t\t\t\t\tDirectory.CreateDirectory( Path.GetDirectoryName( fullMp3Path ) );\r\n\r\n\t\t\t\t\t\tawait File.WriteAllBytesAsync( fullMp3Path, bytes );\r\n\t\t\t\t\t\tLog.Info( \u0022File saved to: \u0022 \u002B fullMp3Path );\r\n\r\n\t\t\t\t\t\tvar mp3Asset = AssetSystem.RegisterFile( fullMp3Path );\r\n\t\t\t\t\t\tif ( mp3Asset != null )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar soundFile = SoundFile.Load( mp3Asset.RelativePath );\r\n\t\t\t\t\t\t\tif ( soundFile != null )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tfinished(soundFile);\r\n\t\t\t\t\t\t\t\tLog.Info( $\u0022Successfully generated and linked sound: {mp3Asset.RelativePath}\u0022 );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tLog.Error( \u0022Failed to load generated sound file\u0022 );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tLog.Error( \u0022Failed to register generated mp3 file as asset\u0022 );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar errorContent = await response.Content.ReadAsStringAsync();\r\n\t\t\t\t\t\tLog.Error( $\u0022Failed to generate sound: {response.StatusCode}. Error: {errorContent}\u0022 );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tcatch ( Exception ex )\r\n\t\t\t\t{\r\n\t\t\t\t\tLog.Error( $\u0022Error generating sound: {ex.Message}\u0022 );\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t\u0022Enter duration in seconds (0.5 to 22):\u0022,\r\n\t\t\t\u0022Generate\u0022,\r\n\t\t\t\u0022Cancel\u0022,\r\n\t\t\tpreviousSoundGenerationDuration,\r\n\t\t\t\u0022Set Sound Duration\u0022 );\r\n\t\t},\r\n\t\t\u0022Describe the sound effect to generate:\u0022,\r\n\t\t\u0022Next\u0022,\r\n\t\t\u0022Cancel\u0022,\r\n\t\tdefaultInput,\r\n\t\t\u0022Generate Sound Effect\u0022 );\r\n\t}\r\n\r\n\t// [ContextMenuFor( typeof( SoundFile ), \u0022Split sound\u0022, \u0022content_cut\u0022 )]\r\n\tpublic static async void SplitSound( Widget parent, SerializedProperty property )\r\n\t{\r\n\t\tvar soundResource = property.GetValue\u003CSoundFile\u003E();\r\n\t\tif ( soundResource == null || !soundResource.IsValid )\r\n\t\t{\r\n\t\t\tLog.Error( \u0022No valid sound file selected\u0022 );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar dialog = new Dialog();\r\n\t\tdialog.Window.Title = \u0022Split Sound\u0022;\r\n\t\tdialog.Window.Size = new Vector2( 800, 400 );\r\n\r\n\t\tdialog.Layout = Layout.Column();\r\n\t\tvar mainLayout = dialog.Layout.AddColumn();\r\n\t\tmainLayout.Margin = 16f;\r\n\r\n\t\tvar loadingLabel = new Label( \u0022Loading audio data...\u0022 );\r\n\t\tmainLayout.Add( loadingLabel );\r\n\r\n\t\tdialog.Show();\r\n\r\n\t\tvar samples = await soundResource.GetSamplesAsync();\r\n\t\tif ( samples == null )\r\n\t\t{\r\n\t\t\tloadingLabel.Text = \u0022Failed to load audio samples\u0022;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tloadingLabel.Destroy();\r\n\r\n\t\tvar spectogramWidget = new SpectogramWidget( soundResource );\r\n\t\tmainLayout.Add( spectogramWidget, 1 );\r\n\r\n\t\tvar controlsLayout = mainLayout.AddRow();\r\n\r\n\t\tvar splitButton = new Button.Primary( \u0022Split\u0022, \u0022content_cut\u0022 );\r\n\t\tcontrolsLayout.Add( splitButton );\r\n\r\n\t\tsplitButton.Clicked = () =\u003E\r\n\t\t{\r\n\t\t\tvar splitPoints = spectogramWidget.GetSplitPoints();\r\n\t\t\tif ( splitPoints.Count \u003C 1 ) return;\r\n\r\n\t\t\tvar listControlWidget = parent.GetAncestor\u003CListControlWidget\u003E();\r\n\t\t\tif ( listControlWidget == null )\r\n\t\t\t{\r\n\t\t\t\tLog.Error( \u0022Could not find ListControlWidget parent\u0022 );\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tvar baseFileName = Path.GetFileNameWithoutExtension( soundResource.ResourcePath );\r\n\t\t\t\tvar outputDir = Path.Combine(\r\n\t\t\t\t\tProject.Current.GetAssetsPath(),\r\n\t\t\t\t\t\u0022generated\u0022,\r\n\t\t\t\t\t$\u0022{baseFileName}_splits\u0022\r\n\t\t\t\t);\r\n\t\t\t\tDirectory.CreateDirectory( outputDir );\r\n\r\n\t\t\t\tvar newSoundFiles = new List\u003CSoundFile\u003E();\r\n\r\n\t\t\t\tfor ( int i = 0; i \u003C splitPoints.Count - 1; i\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar start = splitPoints[i];\r\n\t\t\t\t\tvar end = splitPoints[i \u002B 1];\r\n\t\t\t\t\tvar length = end - start;\r\n\r\n\t\t\t\t\tvar segmentSamples = new short[length];\r\n\t\t\t\t\tArray.Copy( samples, start, segmentSamples, 0, length );\r\n\r\n\t\t\t\t\tvar wavPath = Path.Combine( outputDir, $\u0022{baseFileName}_part_{i \u002B 1}.wav\u0022 );\r\n\r\n\t\t\t\t\tusing ( var writer = new BinaryWriter( File.Create( wavPath ) ) )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\twriter.Write( Encoding.ASCII.GetBytes( \u0022RIFF\u0022 ) );\r\n\t\t\t\t\t\twriter.Write( 36 \u002B (segmentSamples.Length * 2) );\r\n\t\t\t\t\t\twriter.Write( Encoding.ASCII.GetBytes( \u0022WAVE\u0022 ) );\r\n\r\n\t\t\t\t\t\twriter.Write( Encoding.ASCII.GetBytes( \u0022fmt \u0022 ) );\r\n\t\t\t\t\t\twriter.Write( 16 );\r\n\t\t\t\t\t\twriter.Write( (short)1 );\r\n\t\t\t\t\t\twriter.Write( (short)soundResource.Channels );\r\n\t\t\t\t\t\twriter.Write( soundResource.Rate );\r\n\t\t\t\t\t\twriter.Write( soundResource.Rate * soundResource.Channels * 2 );\r\n\t\t\t\t\t\twriter.Write( (short)(soundResource.Channels * 2) );\r\n\t\t\t\t\t\twriter.Write( (short)16 );\r\n\r\n\t\t\t\t\t\twriter.Write( Encoding.ASCII.GetBytes( \u0022data\u0022 ) );\r\n\t\t\t\t\t\twriter.Write( segmentSamples.Length * 2 );\r\n\r\n\t\t\t\t\t\tforeach ( var sample in segmentSamples )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\twriter.Write( sample );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tvar asset = AssetSystem.RegisterFile( wavPath );\r\n\t\t\t\t\tif ( asset != null )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar soundFile = SoundFile.Load( asset.RelativePath );\r\n\t\t\t\t\t\tif ( soundFile != null )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tnewSoundFiles.Add( soundFile );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( newSoundFiles.Count \u003E 0 )\r\n\t\t\t\t{\r\n\t\t\t\t\ttry\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar collectionField = listControlWidget.GetType()\r\n\t\t\t\t\t\t\t.GetFields( BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public )\r\n\t\t\t\t\t\t\t.FirstOrDefault( f =\u003E typeof( SerializedCollection ).IsAssignableFrom( f.FieldType ) );\r\n\r\n\t\t\t\t\t\tif ( collectionField == null )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\treturn;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tvar collection = collectionField.GetValue( listControlWidget ) as SerializedCollection;\r\n\t\t\t\t\t\tif ( collection == null )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\treturn;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif ( property.TryGetAsObject( out var original ) )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tforeach ( var soundFile in newSoundFiles )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tcollection.Add( soundFile );\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\tcollection.Remove( property );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tcatch ( Exception ex )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tLog.Error( $\u0022Error manipulating collection: {ex.Message}\u0022 );\r\n\t\t\t\t\t\tLog.Error( $\u0022Stack trace: {ex.StackTrace}\u0022 );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tdialog.Close();\r\n\t\t\t}\r\n\t\t\tcatch ( Exception ex )\r\n\t\t\t{\r\n\t\t\t\tLog.Error( $\u0022Error splitting sound: {ex.Message}\u0022 );\r\n\t\t\t}\r\n\t\t};\r\n\t}\r\n}"},{"Ident":"attackhelicopter.aitools","Path":"Editor/SoundSettings.cs","FileName":"SoundSettings.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":76432,"IsPrivate":false,"Code":"using System;\r\nusing System.IO;\r\nusing System.Text.Json;\r\nusing Editor;\r\nusing Sandbox;\r\n\r\npublic class SoundSettings : Widget\r\n{\r\n    public class Config : ConfigData\r\n    {\r\n        public string ElevenLabsApiKey { get; set; } = \u0022\u0022;\r\n        public string GenerationPath { get; set; } = \u0022generated\u0022;\r\n    }\r\n\r\n    private LineEdit apiKeyInput;\r\n    private LineEdit pathInput;\r\n    private static Config _settings;\r\n    private Button saveButton;\r\n\r\n    public static Config Settings \r\n    {\r\n        get\r\n        {\r\n            if (_settings == null)\r\n                _settings = EditorUtility.LoadProjectSettings\u003CConfig\u003E(\u0022settings.json\u0022);\r\n            return _settings;\r\n        }\r\n    }\r\n\r\n    public SoundSettings() : base(null)\r\n    {\r\n        Layout = Layout.Column();\r\n        var mainLayout = Layout.Add(Layout.Column());\r\n        mainLayout.Margin = 16;\r\n\r\n        var apiKeyGroup = mainLayout.AddRow();\r\n        apiKeyGroup.Add(new Label(\u0022ElevenLabs API Key:\u0022));\r\n        \r\n        apiKeyInput = new LineEdit(this);\r\n        apiKeyInput.MinimumWidth = 300;\r\n        apiKeyInput.PlaceholderText = \u0022Enter your ElevenLabs API key\u0022;\r\n        apiKeyInput.Text = Settings.ElevenLabsApiKey;\r\n        apiKeyGroup.Add(apiKeyInput);\r\n\r\n        mainLayout.AddSpacingCell(8);\r\n\r\n        var pathGroup = mainLayout.AddRow();\r\n        pathGroup.Add(new Label(\u0022Generation Path:\u0022));\r\n        \r\n        pathInput = new LineEdit(this);\r\n        pathInput.MinimumWidth = 300;\r\n        pathInput.PlaceholderText = \u0022Enter generation path (relative to assets)\u0022;\r\n        pathInput.Text = Settings.GenerationPath;\r\n        pathGroup.Add(pathInput);\r\n\r\n        mainLayout.AddStretchCell();\r\n\r\n        var buttonLayout = mainLayout.AddRow();\r\n        buttonLayout.AddStretchCell();\r\n\r\n        saveButton = new Button.Primary(\u0022Save\u0022, \u0022save\u0022);\r\n        saveButton.Clicked = SaveSettings;\r\n        buttonLayout.Add(saveButton);\r\n    }\r\n\r\n    private void SaveSettings()\r\n    {\r\n        Settings.ElevenLabsApiKey = apiKeyInput.Text;\r\n        Settings.GenerationPath = pathInput.Text;\r\n        EditorUtility.SaveProjectSettings(Settings, \u0022settings.json\u0022);\r\n        Dialog.AskConfirm(() =\u003E {}, \u0022Settings saved successfully.\u0022, \u0022Success\u0022, \u0022Okay\u0022);\r\n    }\r\n\r\n    [Menu(\u0022Editor\u0022, \u0022AI Tools/Settings\u0022)]\r\n    public static void OpenSettings()\r\n    {\r\n        var dialog = new Dialog();\r\n        dialog.Window.Title = \u0022Sound Generation Settings\u0022;\r\n        dialog.Window.Size = new Vector2(500, 200);\r\n        dialog.Layout = Layout.Column();\r\n        dialog.Layout.Add(new SoundSettings());\r\n        dialog.Show();\r\n    }\r\n\r\n    public static string GetGenerationPath()\r\n    {\r\n        return Path.Combine(Project.Current.GetAssetsPath(), Settings.GenerationPath);\r\n    }\r\n}"},{"Ident":"attackhelicopter.aitools","Path":"Editor/AIToolsMenu.cs","FileName":"AIToolsMenu.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":76432,"IsPrivate":false,"Code":"using System.Linq;\r\nusing Editor;\r\nusing Sandbox;\r\n\r\npublic static class AIToolsMenu\r\n{\r\n\t[Menu( \u0022Editor\u0022, \u0022AI Tools/Generate sound\u0022 )]\r\n    public static void Generate()\r\n    {\r\n\t\tif (SoundSettings.Settings.ElevenLabsApiKey.Length \u003C 1)\r\n        {\r\n            Dialog.AskConfirm(() =\u003E {}, \u0022Set your ElevenLabs API key first in settings.\u0022, \u0022Error\u0022, \u0022Okay\u0022);\r\n            return;\r\n        }\r\n\r\n        var inspectorObject = EditorUtility.InspectorObject as Asset;\r\n        if (inspectorObject == null)\r\n        {\r\n            Dialog.AskConfirm(() =\u003E {}, \u0022Open SoundEvent in Inspector first.\u0022, \u0022Error\u0022, \u0022Okay\u0022);\r\n            return;\r\n        }\r\n\r\n        var resource = inspectorObject.LoadResource();\r\n        if (resource == null)\r\n        {\r\n            Dialog.AskConfirm(() =\u003E {}, \u0022Failed to load resource.\u0022, \u0022Error\u0022, \u0022Okay\u0022);\r\n            return;\r\n        }\r\n\r\n        if (!(resource is SoundEvent))\r\n        {\r\n            Dialog.AskConfirm(() =\u003E {}, \u0022Selected asset must be a SoundEvent.\u0022, \u0022Error\u0022, \u0022Okay\u0022);\r\n            return;\r\n        }\r\n\r\n        var soundEvent = resource as SoundEvent;\r\n        SoundAddons.Generate(soundEvent, (soundFile) =\u003E {\r\n            soundEvent.Sounds.Add(soundFile);\r\n\t\t\tEditorUtility.InspectorObject = null;\r\n            EditorUtility.InspectorObject = inspectorObject;\r\n        });\r\n    }\r\n\r\n[Menu(\u0022Editor\u0022, \u0022AI Tools/Split sound\u0022)]\r\n    public static void Split()\r\n    {\r\n        var asset = EditorUtility.InspectorObject as Asset;\r\n        var soundEvent = asset.LoadResource() as SoundEvent;\r\n\r\n        var dialog = new Dialog();\r\n        dialog.Window.Title = \u0022Split Sound\u0022;\r\n        dialog.Window.Size = new Vector2(800, 400);\r\n\r\n        dialog.Layout = Layout.Column();\r\n        var mainLayout = dialog.Layout.AddColumn();\r\n        mainLayout.Margin = 16f;\r\n\r\n        var spectogramWidget = new SpectogramWidget(null);\r\n        mainLayout.Add(spectogramWidget, 1);\r\n\r\n        var controlsLayout = mainLayout.AddRow();\r\n        var splitButton = new Button.Primary(\u0022Split\u0022, \u0022content_cut\u0022);\r\n        controlsLayout.Add(splitButton);\r\n\r\n        splitButton.Clicked = () =\u003E\r\n        {\r\n            if (spectogramWidget.CurrentSound == null)\r\n            {\r\n                Log.Error(\u0022No sound loaded to split\u0022);\r\n                return;\r\n            }\r\n\r\n            spectogramWidget.SplitCurrentSound((soundFile) =\u003E {\r\n                soundEvent.Sounds.Add(soundFile);\r\n\t\t\t\tEditorUtility.InspectorObject = null;\r\n\t\t\t\tEditorUtility.InspectorObject = asset;\r\n            });\r\n        };\r\n\r\n        dialog.Show();\r\n    }\r\n}\r\n"}]}