Example #1
0
        static unsafe void Main(string[] args)
        {
#if FAMISTUDIO_WINDOWS
            try
            {
                // This is only supported in Windows 8.1+.
                SetProcessDpiAwareness(1 /*Process_System_DPI_Aware*/);
            }
            catch { }
#endif

            Settings.Load();
            Cursors.Initialize();
            RenderTheme.Initialize();
            PlatformUtils.Initialize();

#if FAMISTUDIO_WINDOWS
            PerformanceCounter.Initialize();
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
#endif

            var famiStudio = new FamiStudio(args.Length > 0 ? args[0] : null);
            famiStudio.Run();

            Settings.Save();
        }
Example #2
0
        protected override void OnRenderInitialized(RenderGraphics g)
        {
            theme = RenderTheme.CreateResourcesForGraphics(g);

            bmpSong = g.CreateBitmapFromResource("Music");
            bmpInstrument[Project.ExpansionNone] = g.CreateBitmapFromResource("Instrument");
            bmpInstrument[Project.ExpansionVrc6] = g.CreateBitmapFromResource("InstrumentVRC6");
#if DEV
            bmpInstrument[Project.ExpansionVrc7]    = g.CreateBitmapFromResource("InstrumentVRC6");
            bmpInstrument[Project.ExpansionFds]     = g.CreateBitmapFromResource("Instrument");
            bmpInstrument[Project.ExpansionMmc5]    = g.CreateBitmapFromResource("Instrument");
            bmpInstrument[Project.ExpansionNamco]   = g.CreateBitmapFromResource("Instrument");
            bmpInstrument[Project.ExpansionSunsoft] = g.CreateBitmapFromResource("Instrument");
#endif
            bmpAdd            = g.CreateBitmapFromResource("Add");
            bmpDPCM           = g.CreateBitmapFromResource("DPCM");
            bmpDuty[0]        = g.CreateBitmapFromResource("Duty0");
            bmpDuty[1]        = g.CreateBitmapFromResource("Duty1");
            bmpDuty[2]        = g.CreateBitmapFromResource("Duty2");
            bmpDuty[3]        = g.CreateBitmapFromResource("Duty3");
            bmpDuty[4]        = g.CreateBitmapFromResource("Duty4");
            bmpDuty[5]        = g.CreateBitmapFromResource("Duty5");
            bmpDuty[6]        = g.CreateBitmapFromResource("Duty6");
            bmpDuty[7]        = g.CreateBitmapFromResource("Duty7");
            bmpArpeggio       = g.CreateBitmapFromResource("Arpeggio");
            bmpPitch          = g.CreateBitmapFromResource("Pitch");
            bmpVolume         = g.CreateBitmapFromResource("Volume");
            bmpLoadInstrument = g.CreateBitmapFromResource("InstrumentOpen");
        }
Example #3
0
        protected override void OnRenderInitialized(RenderGraphics g)
        {
            theme = RenderTheme.CreateResourcesForGraphics(g);

            bmpTracks[Channel.Square1]  = g.CreateBitmapFromResource("Square");
            bmpTracks[Channel.Square2]  = g.CreateBitmapFromResource("Square");
            bmpTracks[Channel.Triangle] = g.CreateBitmapFromResource("Triangle");
            bmpTracks[Channel.Noise]    = g.CreateBitmapFromResource("Noise");
            bmpTracks[Channel.DPCM]     = g.CreateBitmapFromResource("DPCM");

            bmpGhostNote = g.CreateBitmapFromResource("GhostSmall");

            seekBarBrush                  = g.CreateSolidBrush(ThemeBase.SeekBarColor);
            whiteKeyBrush                 = g.CreateHorizontalGradientBrush(0, trackNameSizeX, ThemeBase.LightGreyFillColor1, ThemeBase.LightGreyFillColor2);
            patternHeaderBrush            = g.CreateVerticalGradientBrush(0, patternHeaderSizeY, ThemeBase.LightGreyFillColor1, ThemeBase.LightGreyFillColor2);
            selectedPatternVisibleBrush   = g.CreateSolidBrush(Color.FromArgb(64, ThemeBase.LightGreyFillColor1));
            selectedPatternInvisibleBrush = g.CreateSolidBrush(Color.FromArgb(32, ThemeBase.LightGreyFillColor1));

            seekGeometry = g.CreateConvexPath(new[]
            {
                new Point(-headerSizeY / 2, 1),
                new Point(0, headerSizeY - 2),
                new Point(headerSizeY / 2, 1)
            });
        }
Example #4
0
        static unsafe void Main(string[] args)
        {
#if FAMISTUDIO_WINDOWS
            try
            {
                // This is only supported in Windows 8.1+.
                SetProcessDpiAwareness(1 /*Process_System_DPI_Aware*/);
            }
            catch { }

            if (!PlatformUtils.IsVS2015RuntimeInstalled())
            {
                if (MessageBox.Show("You seem to be missing the VS 2015 C++ Runtime which is required to run FamiStudio, would you like to visit the FamiStudio website for instruction on how to install it?", "Missing Component", MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    Utils.OpenUrl("https://famistudio.org/doc/#windows");
                }

                return;
            }

            if (!XAudio2Stream.TryDetectXAudio2())
            {
                if (MessageBox.Show("You seem to be missing parts of DirectX which is required to run FamiStudio, would you like to visit the FamiStudio website for instruction on how to install it?", "Missing Component", MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    Utils.OpenUrl("https://famistudio.org/doc/#windows");
                }

                return;
            }
#endif

            Settings.Load();
            PlatformUtils.Initialize();
            RenderTheme.Initialize();
            NesApu.InitializeNoteTables();

#if FAMISTUDIO_WINDOWS
            WinUtils.Initialize();
            PerformanceCounter.Initialize();
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
#elif FAMISTUDIO_LINUX
            LinuxUtils.SetProcessName("FamiStudio");
#endif

            var cli = new CommandLineInterface(args);

            if (!cli.Run())
            {
                var famiStudio = new FamiStudio(args.Length > 0 ? args[0] : null);
                famiStudio.Run();
            }

            Settings.Save();

#if FAMISTUDIO_LINUX
            // We sometimes gets stuck here on Linux, lets abort.
            Environment.Exit(0);
#endif
        }
Example #5
0
        protected override void OnRenderInitialized(RenderGraphics g)
        {
            theme = RenderTheme.CreateResourcesForGraphics(g);

            bmpButtonIcons[(int)ButtonType.Song]       = g.CreateBitmapFromResource("Music");
            bmpButtonIcons[(int)ButtonType.Instrument] = g.CreateBitmapFromResource("Pattern");

            bmpSubButtonIcons[(int)SubButtonType.Add]        = g.CreateBitmapFromResource("Add");
            bmpSubButtonIcons[(int)SubButtonType.DPCM]       = g.CreateBitmapFromResource("DPCM");
            bmpSubButtonIcons[(int)SubButtonType.DutyCycle0] = g.CreateBitmapFromResource("Duty0");
            bmpSubButtonIcons[(int)SubButtonType.DutyCycle1] = g.CreateBitmapFromResource("Duty1");
            bmpSubButtonIcons[(int)SubButtonType.DutyCycle2] = g.CreateBitmapFromResource("Duty2");
            bmpSubButtonIcons[(int)SubButtonType.DutyCycle3] = g.CreateBitmapFromResource("Duty3");
            bmpSubButtonIcons[(int)SubButtonType.Arpeggio]   = g.CreateBitmapFromResource("Arpeggio");
            bmpSubButtonIcons[(int)SubButtonType.Pitch]      = g.CreateBitmapFromResource("Pitch");
            bmpSubButtonIcons[(int)SubButtonType.Volume]     = g.CreateBitmapFromResource("Volume");
        }
Example #6
0
        protected override void OnRenderInitialized(RenderGraphics g)
        {
            theme = RenderTheme.CreateResourcesForGraphics(g);

            bmpTracks[Channel.Square1]  = g.CreateBitmapFromResource("Square");
            bmpTracks[Channel.Square2]  = g.CreateBitmapFromResource("Square");
            bmpTracks[Channel.Triangle] = g.CreateBitmapFromResource("Triangle");
            bmpTracks[Channel.Noise]    = g.CreateBitmapFromResource("Noise");
            bmpTracks[Channel.DPCM]     = g.CreateBitmapFromResource("DPCM");

            bmpGhostNote = g.CreateBitmapFromResource("GhostSmall");

            playPositionBrush    = g.CreateSolidBrush(Color.FromArgb(192, ThemeBase.LightGreyFillColor1));
            whiteKeyBrush        = g.CreateHorizontalGradientBrush(0, trackNameSizeX, ThemeBase.LightGreyFillColor1, ThemeBase.LightGreyFillColor2);
            patternHeaderBrush   = g.CreateVerticalGradientBrush(0, patternHeaderSizeY, ThemeBase.LightGreyFillColor1, ThemeBase.LightGreyFillColor2);
            selectedPatternBrush = g.CreateSolidBrush(Color.FromArgb(128, ThemeBase.LightGreyFillColor1));
        }
Example #7
0
        static unsafe void Main(string[] args)
        {
#if FAMISTUDIO_WINDOWS
            try
            {
                // This is only supported in Windows 8.1+.
                SetProcessDpiAwareness(1 /*Process_System_DPI_Aware*/);
            }
            catch { }
#endif

            Settings.Load();
            RenderTheme.Initialize();
            PlatformUtils.Initialize();
            Cursors.Initialize();
            FamiStudioTempoUtils.Initialize();
            NesApu.InitializeNoteTables();

#if FAMISTUDIO_WINDOWS
            WinUtils.Initialize();
            PerformanceCounter.Initialize();
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
#endif
#if FAMISTUDIO_LINUX
            LinuxUtils.SetProcessName("FamiStudio");
#endif

            var cli = new CommandLineInterface(args);

            if (!cli.Run())
            {
                var famiStudio = new FamiStudio(args.Length > 0 ? args[0] : null);
                famiStudio.Run();
            }

            Settings.Save();

#if FAMISTUDIO_LINUX
            // We sometimes gets stuck here on Linux, lets abort.
            Environment.Exit(0);
#endif
        }
Example #8
0
        protected override void OnRenderInitialized(RenderGraphics g)
        {
            theme        = RenderTheme.CreateResourcesForGraphics(g);
            toolbarBrush = g.CreateHorizontalGradientBrush(0, 81, ThemeBase.LightGreyFillColor1, ThemeBase.LightGreyFillColor2);

            bmpLoopNone    = g.CreateBitmapFromResource("LoopNone");
            bmpLoopSong    = g.CreateBitmapFromResource("Loop");
            bmpLoopPattern = g.CreateBitmapFromResource("LoopPattern");
            bmpPlay        = g.CreateBitmapFromResource("Play");
            bmpPause       = g.CreateBitmapFromResource("Pause");

            buttons[ButtonNew] = new Button {
                X = 4, Y = 4, Bmp = g.CreateBitmapFromResource("File"), Click = OnNew
            };
            buttons[ButtonOpen] = new Button {
                X = 44, Y = 4, Bmp = g.CreateBitmapFromResource("Open"), Click = OnOpen
            };
            buttons[ButtonSave] = new Button {
                X = 84, Y = 4, Bmp = g.CreateBitmapFromResource("Save"), Click = OnSave, RightClick = OnSaveAs
            };
            buttons[ButtonExport] = new Button {
                X = 124, Y = 4, Bmp = g.CreateBitmapFromResource("Export"), Click = OnExport
            };
            buttons[ButtonUndo] = new Button {
                X = 164, Y = 4, Bmp = g.CreateBitmapFromResource("Undo"), Click = OnUndo, Enabled = OnUndoEnabled
            };
            buttons[ButtonRedo] = new Button {
                X = 204, Y = 4, Bmp = g.CreateBitmapFromResource("Redo"), Click = OnRedo, Enabled = OnRedoEnabled
            };
            buttons[ButtonConfig] = new Button {
                X = 244, Y = 4, Bmp = g.CreateBitmapFromResource("Config"), Click = OnConfig
            };
            buttons[ButtonPlay] = new Button {
                X = 476, Y = 4, Click = OnPlay, GetBitmap = OnPlayGetBitmap
            };
            buttons[ButtonRewind] = new Button {
                X = 516, Y = 4, Bmp = g.CreateBitmapFromResource("Rewind"), Click = OnRewind
            };
            buttons[ButtonLoop] = new Button {
                X = 556, Y = 4, Click = OnLoop, GetBitmap = OnLoopGetBitmap
            };

            buttons[ButtonNew].ToolTip    = "New Project (Ctrl-N)";
            buttons[ButtonOpen].ToolTip   = "Open Project (Ctrl-O)";
            buttons[ButtonSave].ToolTip   = "Save Project (Ctrl-S) [Right-Click: Save As...]";
            buttons[ButtonExport].ToolTip = "Export to various formats (Ctrl+E)";
            buttons[ButtonUndo].ToolTip   = "Undo (Ctrl+Z)";
            buttons[ButtonRedo].ToolTip   = "Redo (Ctrl+Y)";
            buttons[ButtonConfig].ToolTip = "Edit Application Settings";
            buttons[ButtonPlay].ToolTip   = "Play/Pause (Space) [Ctrl+Space: Play pattern loop, Shift-Space: Play song loop]";
            buttons[ButtonRewind].ToolTip = "Rewind (Home) [Ctrl+Home: Rewind to beginning of current pattern]";
            buttons[ButtonLoop].ToolTip   = "Toggle Loop Mode";

            var scaling = RenderTheme.MainWindowScaling;

            for (int i = 0; i < ButtonCount; i++)
            {
                var btn = buttons[i];
                btn.X    = (int)(btn.X * scaling);
                btn.Y    = (int)(btn.Y * scaling);
                btn.Size = (int)(btn.Size * scaling);
            }

            timecodePosX     = (int)(DefaultTimecodePosX * scaling);
            timecodePosY     = (int)(DefaultTimecodePosY * scaling);
            timecodeSizeX    = (int)(DefaultTimecodeSizeX * scaling);
            timecodeTextPosX = (int)(DefaultTimecodeTextPosX * scaling);
            tooltipPosY      = (int)(DefaultTooltipPosY * scaling);
        }
Example #9
0
        protected override void OnRenderInitialized(RenderGraphics g)
        {
            theme = RenderTheme.CreateResourcesForGraphics(g);

            toolbarBrush = g.CreateHorizontalGradientBrush(0, 81, ThemeBase.LightGreyFillColor1, ThemeBase.LightGreyFillColor2);
            warningBrush = g.CreateSolidBrush(ThemeBase.Darken(ThemeBase.CustomColors[0, 0]));

            bmpLoopNone    = g.CreateBitmapFromResource("LoopNone");
            bmpLoopSong    = g.CreateBitmapFromResource("Loop");
            bmpLoopPattern = g.CreateBitmapFromResource("LoopPattern");
            bmpPlay        = g.CreateBitmapFromResource("Play");
            bmpPause       = g.CreateBitmapFromResource("Pause");
            bmpNtsc        = g.CreateBitmapFromResource("NTSC");
            bmpPal         = g.CreateBitmapFromResource("PAL");

            buttons[ButtonNew] = new Button {
                X = 4, Y = 4, Bmp = g.CreateBitmapFromResource("File"), Click = OnNew
            };
            buttons[ButtonOpen] = new Button {
                X = 40, Y = 4, Bmp = g.CreateBitmapFromResource("Open"), Click = OnOpen
            };
            buttons[ButtonSave] = new Button {
                X = 76, Y = 4, Bmp = g.CreateBitmapFromResource("Save"), Click = OnSave, RightClick = OnSaveAs
            };
            buttons[ButtonExport] = new Button {
                X = 112, Y = 4, Bmp = g.CreateBitmapFromResource("Export"), Click = OnExport
            };
            buttons[ButtonCopy] = new Button {
                X = 148, Y = 4, Bmp = g.CreateBitmapFromResource("Copy"), Click = OnCopy, Enabled = OnCopyEnabled
            };
            buttons[ButtonCut] = new Button {
                X = 184, Y = 4, Bmp = g.CreateBitmapFromResource("Cut"), Click = OnCut, Enabled = OnCutEnabled
            };
            buttons[ButtonPaste] = new Button {
                X = 220, Y = 4, Bmp = g.CreateBitmapFromResource("Paste"), Click = OnPaste, RightClick = OnPasteSpecial, Enabled = OnPasteEnabled
            };
            buttons[ButtonUndo] = new Button {
                X = 256, Y = 4, Bmp = g.CreateBitmapFromResource("Undo"), Click = OnUndo, Enabled = OnUndoEnabled
            };
            buttons[ButtonRedo] = new Button {
                X = 292, Y = 4, Bmp = g.CreateBitmapFromResource("Redo"), Click = OnRedo, Enabled = OnRedoEnabled
            };
            buttons[ButtonTransform] = new Button {
                X = 328, Y = 4, Bmp = g.CreateBitmapFromResource("Transform"), Click = OnTransform
            };
            buttons[ButtonConfig] = new Button {
                X = 364, Y = 4, Bmp = g.CreateBitmapFromResource("Config"), Click = OnConfig
            };
            buttons[ButtonPlay] = new Button {
                X = 572, Y = 4, Click = OnPlay, GetBitmap = OnPlayGetBitmap
            };
            buttons[ButtonRewind] = new Button {
                X = 608, Y = 4, Bmp = g.CreateBitmapFromResource("Rewind"), Click = OnRewind
            };
            buttons[ButtonLoop] = new Button {
                X = 644, Y = 4, Click = OnLoop, GetBitmap = OnLoopGetBitmap
            };
            buttons[ButtonMachine] = new Button {
                X = 680, Y = 4, Click = OnMachine, GetBitmap = OnMachineGetBitmap, Enabled = OnMachineEnabled
            };
            buttons[ButtonHelp] = new Button {
                X = 36, Y = 4, Bmp = g.CreateBitmapFromResource("Help"), RightAligned = true, Click = OnHelp
            };

            buttons[ButtonNew].ToolTip       = "{MouseLeft} New Project {Ctrl} {N}";
            buttons[ButtonOpen].ToolTip      = "{MouseLeft} Open Project {Ctrl} {O}";
            buttons[ButtonSave].ToolTip      = "{MouseLeft} Save Project {Ctrl} {S} - {MouseRight} Save As...";
            buttons[ButtonExport].ToolTip    = "{MouseLeft} Export to various formats {Ctrl} {E}";
            buttons[ButtonCopy].ToolTip      = "{MouseLeft} Copy selection {Ctrl} {C}";
            buttons[ButtonCut].ToolTip       = "{MouseLeft} Cut selection {Ctrl} {X}";
            buttons[ButtonPaste].ToolTip     = "{MouseLeft} Paste {Ctrl} {V}\n{MouseRight} Paste Special... {Ctrl} {Shift} {V}";
            buttons[ButtonUndo].ToolTip      = "{MouseLeft} Undo {Ctrl} {Z}";
            buttons[ButtonRedo].ToolTip      = "{MouseLeft} Redo {Ctrl} {Y}";
            buttons[ButtonTransform].ToolTip = "{MouseLeft} Perform cleanup and various operations";
            buttons[ButtonConfig].ToolTip    = "{MouseLeft} Edit Application Settings";
            buttons[ButtonPlay].ToolTip      = "{MouseLeft} Play/Pause {Space} - Play from start of pattern {Ctrl} {Space}";
            buttons[ButtonRewind].ToolTip    = "{MouseLeft} Rewind {Home}\nRewind to beginning of current pattern {Ctrl} {Home}";
            buttons[ButtonLoop].ToolTip      = "{MouseLeft} Toggle Loop Mode";
            buttons[ButtonMachine].ToolTip   = "{MouseLeft} Toggle between NTSC/PAL playback mode";
            buttons[ButtonHelp].ToolTip      = "{MouseLeft} Online documentation";

            var scaling = RenderTheme.MainWindowScaling;

            for (int i = 0; i < ButtonCount; i++)
            {
                var btn = buttons[i];
                btn.X    = (int)(btn.X * scaling);
                btn.Y    = (int)(btn.Y * scaling);
                btn.Size = (int)(btn.Size * scaling);
            }

            timecodePosX            = (int)(DefaultTimecodePosX * scaling);
            timecodePosY            = (int)(DefaultTimecodePosY * scaling);
            timecodeSizeX           = (int)(DefaultTimecodeSizeX * scaling);
            tooltipSingleLinePosY   = (int)(DefaultTooltipSingleLinePosY * scaling);
            tooltipMultiLinePosY    = (int)(DefaultTooltipMultiLinePosY * scaling);
            tooltipLineSizeY        = (int)(DefaultTooltipLineSizeY * scaling);
            tooltipSpecialCharSizeX = (int)(DefaultTooltipSpecialCharSizeX * scaling);
            tooltipSpecialCharSizeY = (int)(DefaultTooltipSpecialCharSizeY * scaling);

            specialCharacters["Shift"] = new TooltipSpecialCharacter {
                Width = (int)(32 * scaling)
            };
            specialCharacters["Space"] = new TooltipSpecialCharacter {
                Width = (int)(38 * scaling)
            };
            specialCharacters["Home"] = new TooltipSpecialCharacter {
                Width = (int)(38 * scaling)
            };
            specialCharacters["Ctrl"] = new TooltipSpecialCharacter {
                Width = (int)(28 * scaling)
            };
            specialCharacters["Alt"] = new TooltipSpecialCharacter {
                Width = (int)(24 * scaling)
            };
            specialCharacters["Drag"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("Drag"), OffsetY = 2 * scaling
            };
            specialCharacters["MouseLeft"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("MouseLeft"), OffsetY = 2 * scaling
            };
            specialCharacters["MouseRight"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("MouseRight"), OffsetY = 2 * scaling
            };
            specialCharacters["MouseWheel"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("MouseWheel"), OffsetY = 2 * scaling
            };
            specialCharacters["Warning"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("Warning")
            };

            for (char i = 'A'; i <= 'Z'; i++)
            {
                specialCharacters[i.ToString()] = new TooltipSpecialCharacter {
                    Width = tooltipSpecialCharSizeX
                }
            }
            ;
            for (char i = '0'; i <= '9'; i++)
            {
                specialCharacters[i.ToString()] = new TooltipSpecialCharacter {
                    Width = tooltipSpecialCharSizeX
                }
            }
            ;

            specialCharacters["~"] = new TooltipSpecialCharacter {
                Width = tooltipSpecialCharSizeX
            };

            foreach (var specialChar in specialCharacters.Values)
            {
                if (specialChar.Bmp != null)
                {
                    specialChar.Width = (int)g.GetBitmapWidth(specialChar.Bmp);
                }
                specialChar.Height = tooltipSpecialCharSizeY;
            }
        }
Example #10
0
        public unsafe bool Save(Project originalProject, int songId, int loopCount, string ffmpegExecutable, string filename, int resX, int resY, bool halfFrameRate, int channelMask, int audioBitRate, int videoBitRate, int pianoRollZoom)
        {
            if (channelMask == 0 || loopCount < 1)
            {
                return(false);
            }

            Log.LogMessage(LogSeverity.Info, "Detecting FFmpeg...");

            if (!DetectFFmpeg(ffmpegExecutable))
            {
                return(false);
            }

            videoResX = resX;
            videoResY = resY;

            var project = originalProject.DeepClone();
            var song    = project.GetSong(songId);

            ExtendSongForLooping(song, loopCount);

            Log.LogMessage(LogSeverity.Info, "Initializing channels...");

            var frameRateNumerator = song.Project.PalMode ? 5000773 : 6009883;

            if (halfFrameRate)
            {
                frameRateNumerator /= 2;
            }
            var frameRate = frameRateNumerator.ToString() + "/100000";

            var numChannels        = Utils.NumberOfSetBits(channelMask);
            var channelResXFloat   = videoResX / (float)numChannels;
            var channelResX        = videoResY;
            var channelResY        = (int)channelResXFloat;
            var longestChannelName = 0.0f;

            var videoGraphics = RenderGraphics.Create(videoResX, videoResY, true);

            if (videoGraphics == null)
            {
                Log.LogMessage(LogSeverity.Error, "Error initializing off-screen graphics, aborting.");
                return(false);
            }

            var theme        = RenderTheme.CreateResourcesForGraphics(videoGraphics);
            var bmpWatermark = videoGraphics.CreateBitmapFromResource("VideoWatermark");

            // Generate WAV data for each individual channel for the oscilloscope.
            var channelStates = new List <VideoChannelState>();

            List <short[]> channelsWavData = new List <short[]>();
            var            maxAbsSample    = 0;

            for (int i = 0, channelIndex = 0; i < song.Channels.Length; i++)
            {
                if ((channelMask & (1 << i)) == 0)
                {
                    continue;
                }

                var pattern = song.Channels[i].PatternInstances[0];
                var state   = new VideoChannelState();

                state.videoChannelIndex = channelIndex;
                state.songChannelIndex  = i;
                state.channel           = song.Channels[i];
                state.patternIndex      = 0;
                state.channelText       = state.channel.Name + (state.channel.IsExpansionChannel ? $" ({song.Project.ExpansionAudioShortName})" : "");
                state.wav      = new WavPlayer(SampleRate, 1, 1 << i).GetSongSamples(song, song.Project.PalMode, -1);
                state.graphics = RenderGraphics.Create(channelResX, channelResY, false);
                state.bitmap   = videoGraphics.CreateBitmapFromOffscreenGraphics(state.graphics);

                channelStates.Add(state);
                channelIndex++;

                // Find maximum absolute value to rescale the waveform.
                foreach (short s in state.wav)
                {
                    maxAbsSample = Math.Max(maxAbsSample, Math.Abs(s));
                }

                // Measure the longest text.
                longestChannelName = Math.Max(longestChannelName, state.graphics.MeasureString(state.channelText, ThemeBase.FontBigUnscaled));
            }

            // Tweak some cosmetic stuff that depends on resolution.
            var smallChannelText = longestChannelName + 32 + ChannelIconTextSpacing > channelResY * 0.8f;
            var bmpSuffix        = smallChannelText ? "" : "@2x";
            var font             = smallChannelText ? ThemeBase.FontMediumUnscaled : ThemeBase.FontBigUnscaled;
            var textOffsetY      = smallChannelText ? 1 : 4;
            var pianoRollScaleX  = Utils.Clamp(resY / 1080.0f, 0.6f, 0.9f);
            var pianoRollScaleY  = channelResY < VeryThinNoteThreshold ? 0.5f : (channelResY < ThinNoteThreshold ? 0.667f : 1.0f);
            var channelLineWidth = channelResY < ThinNoteThreshold ? 3 : 5;
            var gradientSizeY    = 256 * (videoResY / 1080.0f);
            var gradientBrush    = videoGraphics.CreateVerticalGradientBrush(0, gradientSizeY, Color.Black, Color.FromArgb(0, Color.Black));

            foreach (var s in channelStates)
            {
                s.bmpIcon = videoGraphics.CreateBitmapFromResource(ChannelType.Icons[s.channel.Type] + bmpSuffix);
            }

            // Generate the metadata for the video so we know what's happening at every frame
            var metadata = new VideoMetadataPlayer(SampleRate, 1).GetVideoMetadata(song, song.Project.PalMode, -1);

            var oscScale    = maxAbsSample != 0 ? short.MaxValue / (float)maxAbsSample : 1.0f;
            var oscLookback = (metadata[1].wavOffset - metadata[0].wavOffset) / 2;

#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
            var dummyControl = new DummyGLControl();
            dummyControl.Move(0, 0, videoResX, videoResY);
#endif

            // Setup piano roll and images.
            var pianoRoll = new PianoRoll();
#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
            pianoRoll.Move(0, 0, channelResX, channelResY);
#else
            pianoRoll.Width  = channelResX;
            pianoRoll.Height = channelResY;
#endif

            pianoRoll.StartVideoRecording(channelStates[0].graphics, song, pianoRollZoom, pianoRollScaleX, pianoRollScaleY, out var noteSizeY);

            // Build the scrolling data.
            var numVisibleNotes = (int)Math.Floor(channelResY / (float)noteSizeY);
            ComputeChannelsScroll(metadata, channelMask, numVisibleNotes);

            if (song.UsesFamiTrackerTempo)
            {
                SmoothFamitrackerScrolling(metadata);
            }
            else
            {
                SmoothFamiStudioScrolling(metadata, song);
            }

            var videoImage   = new byte[videoResY * videoResX * 4];
            var oscilloscope = new float[channelResY, 2];

            // Start ffmpeg with pipe input.
            var tempFolder    = Utils.GetTemporaryDiretory();
            var tempAudioFile = Path.Combine(tempFolder, "temp.wav");

#if !DEBUG
            try
#endif
            {
                Log.LogMessage(LogSeverity.Info, "Exporting audio...");

                // Save audio to temporary file.
                WavMp3ExportUtils.Save(song, tempAudioFile, SampleRate, 1, -1, channelMask, false, false, (samples, fn) => { WaveFile.Save(samples, fn, SampleRate); });

                var process = LaunchFFmpeg(ffmpegExecutable, $"-y -f rawvideo -pix_fmt argb -s {videoResX}x{videoResY} -r {frameRate} -i - -i \"{tempAudioFile}\" -c:v h264 -pix_fmt yuv420p -b:v {videoBitRate}K -c:a aac -b:a {audioBitRate}k \"{filename}\"", true, false);

                // Generate each of the video frames.
                using (var stream = new BinaryWriter(process.StandardInput.BaseStream))
                {
                    for (int f = 0; f < metadata.Length; f++)
                    {
                        if (Log.ShouldAbortOperation)
                        {
                            break;
                        }

                        if ((f % 100) == 0)
                        {
                            Log.LogMessage(LogSeverity.Info, $"Rendering frame {f} / {metadata.Length}");
                        }

                        Log.ReportProgress(f / (float)(metadata.Length - 1));

                        if (halfFrameRate && (f & 1) != 0)
                        {
                            continue;
                        }

                        var frame = metadata[f];

                        // Render the piano rolls for each channels.
                        foreach (var s in channelStates)
                        {
                            s.volume = frame.channelVolumes[s.songChannelIndex];
                            s.note   = frame.channelNotes[s.songChannelIndex];

                            var color = Color.Transparent;

                            if (s.note.IsMusical)
                            {
                                if (s.channel.Type == ChannelType.Dpcm)
                                {
                                    var mapping = project.GetDPCMMapping(s.note.Value);
                                    if (mapping != null && mapping.Sample != null)
                                    {
                                        color = mapping.Sample.Color;
                                    }
                                }
                                else
                                {
                                    color = Color.FromArgb(128 + s.volume * 127 / 15, s.note.Instrument != null ? s.note.Instrument.Color : ThemeBase.DarkGreyFillColor2);
                                }
                            }

#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
                            s.graphics.BeginDraw(pianoRoll, channelResY);
#else
                            s.graphics.BeginDraw();
#endif
                            pianoRoll.RenderVideoFrame(s.graphics, Channel.ChannelTypeToIndex(s.channel.Type), frame.playPattern, frame.playNote, frame.scroll[s.songChannelIndex], s.note.Value, color);
                            s.graphics.EndDraw();
                        }

                        // Render the full screen overlay.
#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
                        videoGraphics.BeginDraw(dummyControl, videoResY);
#else
                        videoGraphics.BeginDraw();
#endif
                        videoGraphics.Clear(Color.Black);

                        // Composite the channel renders.
                        foreach (var s in channelStates)
                        {
                            int channelPosX1 = (int)Math.Round((s.videoChannelIndex + 1) * channelResXFloat);
                            videoGraphics.DrawRotatedFlippedBitmap(s.bitmap, channelPosX1, videoResY, s.bitmap.Size.Width, s.bitmap.Size.Height);
                        }

                        // Gradient
                        videoGraphics.FillRectangle(0, 0, videoResX, gradientSizeY, gradientBrush);

                        // Channel names + oscilloscope
                        foreach (var s in channelStates)
                        {
                            int channelPosX0 = (int)Math.Round((s.videoChannelIndex + 0) * channelResXFloat);
                            int channelPosX1 = (int)Math.Round((s.videoChannelIndex + 1) * channelResXFloat);

                            var channelNameSizeX = videoGraphics.MeasureString(s.channelText, font);
                            var channelIconPosX  = channelPosX0 + channelResY / 2 - (channelNameSizeX + s.bmpIcon.Size.Width + ChannelIconTextSpacing) / 2;

                            videoGraphics.FillRectangle(channelIconPosX, ChannelIconPosY, channelIconPosX + s.bmpIcon.Size.Width, ChannelIconPosY + s.bmpIcon.Size.Height, theme.DarkGreyLineBrush2);
                            videoGraphics.DrawBitmap(s.bmpIcon, channelIconPosX, ChannelIconPosY);
                            videoGraphics.DrawText(s.channelText, font, channelIconPosX + s.bmpIcon.Size.Width + ChannelIconTextSpacing, ChannelIconPosY + textOffsetY, theme.LightGreyFillBrush1);

                            if (s.videoChannelIndex > 0)
                            {
                                videoGraphics.DrawLine(channelPosX0, 0, channelPosX0, videoResY, theme.BlackBrush, channelLineWidth);
                            }

                            var oscMinY = (int)(ChannelIconPosY + s.bmpIcon.Size.Height + 10);
                            var oscMaxY = (int)(oscMinY + 100.0f * (resY / 1080.0f));

                            GenerateOscilloscope(s.wav, frame.wavOffset, (int)Math.Round(SampleRate * OscilloscopeWindowSize), oscLookback, oscScale, channelPosX0 + 10, oscMinY, channelPosX1 - 10, oscMaxY, oscilloscope);

                            videoGraphics.AntiAliasing = true;
                            videoGraphics.DrawLine(oscilloscope, theme.LightGreyFillBrush1);
                            videoGraphics.AntiAliasing = false;
                        }

                        // Watermark.
                        videoGraphics.DrawBitmap(bmpWatermark, videoResX - bmpWatermark.Size.Width, videoResY - bmpWatermark.Size.Height);
                        videoGraphics.EndDraw();

                        // Readback + send to ffmpeg.
                        videoGraphics.GetBitmap(videoImage);
                        stream.Write(videoImage);

                        // Dump debug images.
                        // DumpDebugImage(videoImage, videoResX, videoResY, f);
                    }
                }

                process.WaitForExit();
                process.Dispose();
                process = null;

                File.Delete(tempAudioFile);
            }
#if !DEBUG
            catch (Exception e)
            {
                Log.LogMessage(LogSeverity.Error, "Error exporting video.");
                Log.LogMessage(LogSeverity.Error, e.Message);
            }
            finally
#endif
            {
                pianoRoll.EndVideoRecording();
                foreach (var c in channelStates)
                {
                    c.bmpIcon.Dispose();
                    c.bitmap.Dispose();
                    c.graphics.Dispose();
                }
                theme.Terminate();
                bmpWatermark.Dispose();
                gradientBrush.Dispose();
                videoGraphics.Dispose();
            }

            return(true);
        }
Example #11
0
        protected override void OnRenderInitialized(RenderGraphics g)
        {
            theme = RenderTheme.CreateResourcesForGraphics(g);

            toolbarBrush = g.CreateVerticalGradientBrush(0, Height, ThemeBase.DarkGreyFillColor2, ThemeBase.DarkGreyFillColor1);
            warningBrush = g.CreateSolidBrush(System.Drawing.Color.FromArgb(205, 77, 64));

            bmpLoopNone    = g.CreateBitmapFromResource("LoopNone");
            bmpLoopSong    = g.CreateBitmapFromResource("Loop");
            bmpLoopPattern = g.CreateBitmapFromResource("LoopPattern");
            bmpPlay        = g.CreateBitmapFromResource("Play");
            bmpPause       = g.CreateBitmapFromResource("Pause");
            bmpNtsc        = g.CreateBitmapFromResource("NTSC");
            bmpPal         = g.CreateBitmapFromResource("PAL");
            bmpNtscToPal   = g.CreateBitmapFromResource("NTSCToPAL");
            bmpPalToNtsc   = g.CreateBitmapFromResource("PALToNTSC");
            bmpRec         = g.CreateBitmapFromResource("Rec");
            bmpRecRed      = g.CreateBitmapFromResource("RecRed");

            buttons[ButtonNew] = new Button {
                Bmp = g.CreateBitmapFromResource("File"), Click = OnNew
            };
            buttons[ButtonOpen] = new Button {
                Bmp = g.CreateBitmapFromResource("Open"), Click = OnOpen
            };
            buttons[ButtonSave] = new Button {
                Bmp = g.CreateBitmapFromResource("Save"), Click = OnSave, RightClick = OnSaveAs
            };
            buttons[ButtonExport] = new Button {
                Bmp = g.CreateBitmapFromResource("Export"), Click = OnExport, RightClick = OnRepeatLastExport
            };
            buttons[ButtonCopy] = new Button {
                Bmp = g.CreateBitmapFromResource("Copy"), Click = OnCopy, Enabled = OnCopyEnabled
            };
            buttons[ButtonCut] = new Button {
                Bmp = g.CreateBitmapFromResource("Cut"), Click = OnCut, Enabled = OnCutEnabled
            };
            buttons[ButtonPaste] = new Button {
                Bmp = g.CreateBitmapFromResource("Paste"), Click = OnPaste, RightClick = OnPasteSpecial, Enabled = OnPasteEnabled
            };
            buttons[ButtonUndo] = new Button {
                Bmp = g.CreateBitmapFromResource("Undo"), Click = OnUndo, Enabled = OnUndoEnabled
            };
            buttons[ButtonRedo] = new Button {
                Bmp = g.CreateBitmapFromResource("Redo"), Click = OnRedo, Enabled = OnRedoEnabled
            };
            buttons[ButtonTransform] = new Button {
                Bmp = g.CreateBitmapFromResource("Transform"), Click = OnTransform
            };
            buttons[ButtonConfig] = new Button {
                Bmp = g.CreateBitmapFromResource("Config"), Click = OnConfig
            };
            buttons[ButtonPlay] = new Button {
                Click = OnPlay, GetBitmap = OnPlayGetBitmap
            };
            buttons[ButtonRec] = new Button {
                GetBitmap = OnRecordGetBitmap, Click = OnRecord
            };
            buttons[ButtonRewind] = new Button {
                Bmp = g.CreateBitmapFromResource("Rewind"), Click = OnRewind
            };
            buttons[ButtonLoop] = new Button {
                Click = OnLoop, GetBitmap = OnLoopGetBitmap
            };
            buttons[ButtonQwerty] = new Button {
                Bmp = g.CreateBitmapFromResource("QwertyPiano"), Click = OnQwerty, Enabled = OnQwertyEnabled
            };
            buttons[ButtonMachine] = new Button {
                Click = OnMachine, GetBitmap = OnMachineGetBitmap, Enabled = OnMachineEnabled
            };
            buttons[ButtonFollow] = new Button {
                Bmp = g.CreateBitmapFromResource("Follow"), Click = OnFollow, Enabled = OnFollowEnabled
            };
            buttons[ButtonHelp] = new Button {
                Bmp = g.CreateBitmapFromResource("Help"), RightAligned = true, Click = OnHelp
            };

            buttons[ButtonNew].ToolTip       = "{MouseLeft} New Project {Ctrl} {N}";
            buttons[ButtonOpen].ToolTip      = "{MouseLeft} Open Project {Ctrl} {O}";
            buttons[ButtonSave].ToolTip      = "{MouseLeft} Save Project {Ctrl} {S} - {MouseRight} Save As...";
            buttons[ButtonExport].ToolTip    = "{MouseLeft} Export to various formats {Ctrl} {E}\n{MouseRight} Repeat last export {Ctrl} {Shift} {E}";
            buttons[ButtonCopy].ToolTip      = "{MouseLeft} Copy selection {Ctrl} {C}";
            buttons[ButtonCut].ToolTip       = "{MouseLeft} Cut selection {Ctrl} {X}";
            buttons[ButtonPaste].ToolTip     = "{MouseLeft} Paste {Ctrl} {V}\n{MouseRight} Paste Special... {Ctrl} {Shift} {V}";
            buttons[ButtonUndo].ToolTip      = "{MouseLeft} Undo {Ctrl} {Z}";
            buttons[ButtonRedo].ToolTip      = "{MouseLeft} Redo {Ctrl} {Y}";
            buttons[ButtonTransform].ToolTip = "{MouseLeft} Perform cleanup and various operations";
            buttons[ButtonConfig].ToolTip    = "{MouseLeft} Edit Application Settings";
            buttons[ButtonPlay].ToolTip      = "{MouseLeft} Play/Pause {Space} - Play from start of pattern {Ctrl} {Space}\nPlay from start of song {Shift} {Space} - Play from loop point {Ctrl} {Shift} {Space}";
            buttons[ButtonRewind].ToolTip    = "{MouseLeft} Rewind {Home}\nRewind to beginning of current pattern {Ctrl} {Home}";
            buttons[ButtonRec].ToolTip       = "{MouseLeft} Toggles recording mode {Enter}\nAbort recording {Esc}";
            buttons[ButtonLoop].ToolTip      = "{MouseLeft} Toggle Loop Mode";
            buttons[ButtonQwerty].ToolTip    = "{MouseLeft} Toggle QWERTY keyboard piano input";
            buttons[ButtonMachine].ToolTip   = "{MouseLeft} Toggle between NTSC/PAL playback mode";
            buttons[ButtonFollow].ToolTip    = "{MouseLeft} Toggle follow mode {Shift} {F}";
            buttons[ButtonHelp].ToolTip      = "{MouseLeft} Online documentation";

            UpdateButtonLayout();

            var scaling = RenderTheme.MainWindowScaling;

            timecodePosY            = (int)(DefaultTimecodePosY * scaling);
            timecodeSizeX           = (int)(DefaultTimecodeSizeX * scaling);
            tooltipSingleLinePosY   = (int)(DefaultTooltipSingleLinePosY * scaling);
            tooltipMultiLinePosY    = (int)(DefaultTooltipMultiLinePosY * scaling);
            tooltipLineSizeY        = (int)(DefaultTooltipLineSizeY * scaling);
            tooltipSpecialCharSizeX = (int)(DefaultTooltipSpecialCharSizeX * scaling);
            tooltipSpecialCharSizeY = (int)(DefaultTooltipSpecialCharSizeY * scaling);

            specialCharacters["Shift"] = new TooltipSpecialCharacter {
                Width = (int)(32 * scaling)
            };
            specialCharacters["Space"] = new TooltipSpecialCharacter {
                Width = (int)(38 * scaling)
            };
            specialCharacters["Home"] = new TooltipSpecialCharacter {
                Width = (int)(38 * scaling)
            };
            specialCharacters["Ctrl"] = new TooltipSpecialCharacter {
                Width = (int)(28 * scaling)
            };
            specialCharacters["Alt"] = new TooltipSpecialCharacter {
                Width = (int)(24 * scaling)
            };
            specialCharacters["Enter"] = new TooltipSpecialCharacter {
                Width = (int)(38 * scaling)
            };
            specialCharacters["Esc"] = new TooltipSpecialCharacter {
                Width = (int)(24 * scaling)
            };
            specialCharacters["Del"] = new TooltipSpecialCharacter {
                Width = (int)(24 * scaling)
            };
            specialCharacters["Drag"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("Drag"), OffsetY = 2 * scaling
            };
            specialCharacters["MouseLeft"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("MouseLeft"), OffsetY = 2 * scaling
            };
            specialCharacters["MouseRight"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("MouseRight"), OffsetY = 2 * scaling
            };
            specialCharacters["MouseWheel"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("MouseWheel"), OffsetY = 2 * scaling
            };
            specialCharacters["Warning"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("Warning")
            };

            for (char i = 'A'; i <= 'Z'; i++)
            {
                specialCharacters[i.ToString()] = new TooltipSpecialCharacter {
                    Width = tooltipSpecialCharSizeX
                }
            }
            ;
            for (char i = '0'; i <= '9'; i++)
            {
                specialCharacters[i.ToString()] = new TooltipSpecialCharacter {
                    Width = tooltipSpecialCharSizeX
                }
            }
            ;

            specialCharacters["~"] = new TooltipSpecialCharacter {
                Width = tooltipSpecialCharSizeX
            };

            foreach (var specialChar in specialCharacters.Values)
            {
                if (specialChar.Bmp != null)
                {
                    specialChar.Width = (int)g.GetBitmapWidth(specialChar.Bmp);
                }
                specialChar.Height = tooltipSpecialCharSizeY;
            }
        }
Example #12
0
        public unsafe bool Save(Project originalProject, int songId, int loopCount, string ffmpegExecutable, string filename, int channelMask, int audioBitRate, int videoBitRate, int pianoRollZoom, bool thinNotes)
        {
            if (channelMask == 0 || loopCount < 1)
            {
                return(false);
            }

            Log.LogMessage(LogSeverity.Info, "Detecting FFmpeg...");

            if (!DetectFFmpeg(ffmpegExecutable))
            {
                return(false);
            }

            var project = originalProject.DeepClone();
            var song    = project.GetSong(songId);

            ExtendSongForLooping(song, loopCount);

            Log.LogMessage(LogSeverity.Info, "Initializing channels...");

            var frameRate        = song.Project.PalMode ? "5000773/100000" : "6009883/100000";
            var numChannels      = Utils.NumberOfSetBits(channelMask);
            var channelResXFloat = videoResX / (float)numChannels;
            var channelResX      = videoResY;
            var channelResY      = (int)channelResXFloat;

            var channelGraphics = new RenderGraphics(channelResX, channelResY);
            var videoGraphics   = new RenderGraphics(videoResX, videoResY);

            var theme        = RenderTheme.CreateResourcesForGraphics(videoGraphics);
            var bmpWatermark = videoGraphics.CreateBitmapFromResource("VideoWatermark");

            // Generate WAV data for each individual channel for the oscilloscope.
            var channelStates = new List <VideoChannelState>();

            List <short[]> channelsWavData = new List <short[]>();
            var            maxAbsSample    = 0;

            for (int i = 0, channelIndex = 0; i < song.Channels.Length; i++)
            {
                if ((channelMask & (1 << i)) == 0)
                {
                    continue;
                }

                var pattern = song.Channels[i].PatternInstances[0];
                var state   = new VideoChannelState();

                state.videoChannelIndex = channelIndex;
                state.songChannelIndex  = i;
                state.channel           = song.Channels[i];
                state.patternIndex      = 0;
                state.channelText       = state.channel.Name + (state.channel.IsExpansionChannel ? $" ({song.Project.ExpansionAudioShortName})" : "");
                state.bmp = videoGraphics.CreateBitmapFromResource(Channel.ChannelIcons[song.Channels[i].Type] + "@2x"); // HACK: Grab the 200% scaled version directly.
                state.wav = new WavPlayer(sampleRate, 1, 1 << i).GetSongSamples(song, song.Project.PalMode, -1);

                channelStates.Add(state);
                channelIndex++;

                // Find maximum absolute value to rescale the waveform.
                foreach (short s in state.wav)
                {
                    maxAbsSample = Math.Max(maxAbsSample, Math.Abs(s));
                }
            }

            // Generate the metadata for the video so we know what's happening at every frame
            var metadata = new VideoMetadataPlayer(sampleRate, 1).GetVideoMetadata(song, song.Project.PalMode, -1);

            var oscScale    = maxAbsSample != 0 ? short.MaxValue / (float)maxAbsSample : 1.0f;
            var oscLookback = (metadata[1].wavOffset - metadata[0].wavOffset) / 2;

#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
            var dummyControl = new DummyGLControl();
            dummyControl.Move(0, 0, videoResX, videoResY);
#endif

            // Setup piano roll and images.
            var pianoRoll = new PianoRoll();
#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
            pianoRoll.Move(0, 0, channelResX, channelResY);
#else
            pianoRoll.Width  = channelResX;
            pianoRoll.Height = channelResY;
#endif
            pianoRoll.StartVideoRecording(channelGraphics, song, pianoRollZoom, thinNotes, out var noteSizeY);

            // Build the scrolling data.
            var numVisibleNotes = (int)Math.Floor(channelResY / (float)noteSizeY);
            ComputeChannelsScroll(metadata, channelMask, numVisibleNotes);

            if (song.UsesFamiTrackerTempo)
            {
                SmoothFamiTrackerTempo(metadata);
            }

            var videoImage   = new byte[videoResY * videoResX * 4];
            var channelImage = new byte[channelResY * channelResX * 4];
            var oscilloscope = new float[channelResY, 2];

#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
            var badAlpha = DetectBadOpenGLAlpha(dummyControl, videoGraphics, videoImage);
#endif

            // Start ffmpeg with pipe input.
            var tempFolder    = Utils.GetTemporaryDiretory();
            var tempVideoFile = Path.Combine(tempFolder, "temp.h264");
            var tempAudioFile = Path.Combine(tempFolder, "temp.wav");

            try
            {
                var process = LaunchFFmpeg(ffmpegExecutable, $"-y -f rawvideo -pix_fmt argb -s {videoResX}x{videoResY} -r {frameRate} -i - -c:v libx264 -pix_fmt yuv420p -b:v {videoBitRate}M -an \"{tempVideoFile}\"", true, false);

                // Generate each of the video frames.
                using (var stream = new BinaryWriter(process.StandardInput.BaseStream))
                {
                    for (int f = 0; f < metadata.Length; f++)
                    {
                        if (Log.ShouldAbortOperation)
                        {
                            break;
                        }

                        if ((f % 100) == 0)
                        {
                            Log.LogMessage(LogSeverity.Info, $"Rendering frame {f} / {metadata.Length}");
                        }

                        Log.ReportProgress(f / (float)(metadata.Length - 1));

                        var frame = metadata[f];

                        // Render the full screen overlay.
#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
                        videoGraphics.BeginDraw(dummyControl, videoResY);
#else
                        videoGraphics.BeginDraw();
#endif
                        videoGraphics.Clear(Color.FromArgb(0, 0, 0, 0));

                        foreach (var s in channelStates)
                        {
                            int channelPosX0 = (int)Math.Round((s.videoChannelIndex + 0) * channelResXFloat);
                            int channelPosX1 = (int)Math.Round((s.videoChannelIndex + 1) * channelResXFloat);

                            var channelNameSizeX = videoGraphics.MeasureString(s.channelText, ThemeBase.FontBigUnscaled);
                            var channelIconPosX  = channelPosX0 + channelResY / 2 - (channelNameSizeX + s.bmp.Size.Width + channelIconTextSpacing) / 2;

                            videoGraphics.FillRectangle(channelIconPosX, channelIconPosY, channelIconPosX + s.bmp.Size.Width, channelIconPosY + s.bmp.Size.Height, theme.LightGreyFillBrush1);
                            videoGraphics.DrawBitmap(s.bmp, channelIconPosX, channelIconPosY);
                            videoGraphics.DrawText(s.channelText, ThemeBase.FontBigUnscaled, channelIconPosX + s.bmp.Size.Width + channelIconTextSpacing, channelTextPosY, theme.LightGreyFillBrush1);

                            if (s.videoChannelIndex > 0)
                            {
                                videoGraphics.DrawLine(channelPosX0, 0, channelPosX0, videoResY, theme.BlackBrush, 5);
                            }

                            GenerateOscilloscope(s.wav, frame.wavOffset, (int)Math.Round(sampleRate * oscilloscopeWindowSize), oscLookback, oscScale, channelPosX0 + 10, 60, channelPosX1 - 10, 160, oscilloscope);

                            videoGraphics.AntiAliasing = true;
                            videoGraphics.DrawLine(oscilloscope, theme.LightGreyFillBrush1);
                            videoGraphics.AntiAliasing = false;
                        }

                        videoGraphics.DrawBitmap(bmpWatermark, videoResX - bmpWatermark.Size.Width, videoResY - bmpWatermark.Size.Height);
                        videoGraphics.EndDraw();
                        videoGraphics.GetBitmap(videoImage);

                        // Render the piano rolls for each channels.
                        foreach (var s in channelStates)
                        {
                            s.volume = frame.channelVolumes[s.songChannelIndex];
                            s.note   = frame.channelNotes[s.songChannelIndex];

                            var color = Color.Transparent;

                            if (s.note.IsMusical)
                            {
                                if (s.channel.Type == Channel.Dpcm)
                                {
                                    color = Color.FromArgb(210, ThemeBase.MediumGreyFillColor1);
                                }
                                else
                                {
                                    color = Color.FromArgb(128 + s.volume * 127 / 15, s.note.Instrument != null ? s.note.Instrument.Color : ThemeBase.DarkGreyFillColor2);
                                }
                            }

#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
                            channelGraphics.BeginDraw(pianoRoll, channelResY);
#else
                            channelGraphics.BeginDraw();
#endif
                            pianoRoll.RenderVideoFrame(channelGraphics, Channel.ChannelTypeToIndex(s.channel.Type), frame.playPattern, frame.playNote, frame.scroll[s.songChannelIndex], s.note.Value, color);
                            channelGraphics.EndDraw();

                            channelGraphics.GetBitmap(channelImage);

                            // Composite the channel image with the full screen video overlay on the CPU.
                            int channelPosX = (int)Math.Round(s.videoChannelIndex * channelResXFloat);
                            int channelPosY = 0;

                            for (int y = 0; y < channelResY; y++)
                            {
                                for (int x = 0; x < channelResX; x++)
                                {
                                    int videoIdx   = (channelPosY + x) * videoResX * 4 + (channelPosX + y) * 4;
                                    int channelIdx = (channelResY - y - 1) * channelResX * 4 + (channelResX - x - 1) * 4;

                                    byte videoA    = videoImage[videoIdx + 3];
                                    byte gradientA = (byte)(x < 255 ? 255 - x : 0); // Doing the gradient on CPU to look same on GL/D2D.

                                    byte channelR = channelImage[channelIdx + 0];
                                    byte channelG = channelImage[channelIdx + 1];
                                    byte channelB = channelImage[channelIdx + 2];

                                    if (videoA != 0 || gradientA != 0)
                                    {
#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
                                        // Fix bad sRGB alpha.
                                        if (badAlpha)
                                        {
                                            videoA = SRGBToLinear[videoA];
                                        }
#endif
                                        videoA = Math.Max(videoA, gradientA);

                                        int videoR = videoImage[videoIdx + 0];
                                        int videoG = videoImage[videoIdx + 1];
                                        int videoB = videoImage[videoIdx + 2];

                                        // Integer alpha blend.
                                        // Note that alpha is pre-multiplied, so we if we multiply again, image will look aliased.
                                        channelR = (byte)((channelR * (255 - videoA) + videoR * 255 /*videoA*/) >> 8);
                                        channelG = (byte)((channelG * (255 - videoA) + videoG * 255 /*videoA*/) >> 8);
                                        channelB = (byte)((channelB * (255 - videoA) + videoB * 255 /*videoA*/) >> 8);
                                    }

                                    // We byteswap here to match what ffmpeg expect.
                                    videoImage[videoIdx + 3] = channelR;
                                    videoImage[videoIdx + 2] = channelG;
                                    videoImage[videoIdx + 1] = channelB;
                                    videoImage[videoIdx + 0] = 255;

                                    // To export images to debug.
                                    //videoImage[videoIdx + 0] = channelR;
                                    //videoImage[videoIdx + 1] = channelG;
                                    //videoImage[videoIdx + 2] = channelB;
                                    //videoImage[videoIdx + 3] = 255;
                                }
                            }

                            var prevChannelEndPosX = (int)Math.Round((s.videoChannelIndex - 1) * channelResXFloat) + channelResY;

                            // HACK: Since we round the channels positions, we can end up with columns of pixels that arent byteswapped.
                            if (s.videoChannelIndex > 0 && channelPosX != prevChannelEndPosX)
                            {
                                for (int y = 0; y < videoResY; y++)
                                {
                                    int videoIdx = y * videoResX * 4 + (channelPosX - 1) * 4;

                                    byte videoR = videoImage[videoIdx + 0];
                                    byte videoG = videoImage[videoIdx + 1];
                                    byte videoB = videoImage[videoIdx + 2];

                                    videoImage[videoIdx + 3] = videoR;
                                    videoImage[videoIdx + 2] = videoG;
                                    videoImage[videoIdx + 1] = videoB;
                                    videoImage[videoIdx + 0] = 255;
                                }
                            }
                        }

                        stream.Write(videoImage);

                        // Dump debug images.
#if FAMISTUDIO_LINUX || FAMISTUDIO_MACOS
                        //var pb = new Gdk.Pixbuf(channelImage, true, 8, channelResX, channelResY, channelResX * 4);
                        //pb.Save($"/home/mat/Downloads/channel.png", "png");
                        //var pb = new Gdk.Pixbuf(videoImage, true, 8, videoResX, videoResY, videoResX * 4);
                        //pb.Save($"/home/mat/Downloads/frame_{f:D4}.png", "png");
#else
                        //fixed (byte* vp = &videoImage[0])
                        //{
                        //    var b = new System.Drawing.Bitmap(videoResX, videoResY, videoResX * 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, new IntPtr(vp));
                        //    b.Save($"d:\\dump\\pr\\frame_{f:D4}.png");
                        //}
#endif
                    }
                }

                process.WaitForExit();
                process.Dispose();
                process = null;

                Log.LogMessage(LogSeverity.Info, "Exporting audio...");

                // Save audio to temporary file.
                WaveFile.Save(song, tempAudioFile, sampleRate, 1, -1, channelMask);

                Log.LogMessage(LogSeverity.Info, "Mixing audio and video...");

                // Run ffmpeg again to combine audio + video.
                process = LaunchFFmpeg(ffmpegExecutable, $"-y -i \"{tempVideoFile}\" -i \"{tempAudioFile}\" -c:v copy -c:a aac -b:a {audioBitRate}k \"{filename}\"", false, false);
                process.WaitForExit();
                process.Dispose();
                process = null;

                File.Delete(tempAudioFile);
                File.Delete(tempVideoFile);
            }
            catch (Exception e)
            {
                Log.LogMessage(LogSeverity.Error, "Error exporting video.");
                Log.LogMessage(LogSeverity.Error, e.Message);
            }
            finally
            {
                pianoRoll.EndVideoRecording();
                foreach (var c in channelStates)
                {
                    c.bmp.Dispose();
                }
                theme.Terminate();
                bmpWatermark.Dispose();
                channelGraphics.Dispose();
                videoGraphics.Dispose();
            }

            return(true);
        }
Example #13
0
        protected override void OnRenderInitialized(RenderGraphics g)
        {
            theme        = RenderTheme.CreateResourcesForGraphics(g);
            toolbarBrush = g.CreateHorizontalGradientBrush(0, 81, ThemeBase.LightGreyFillColor1, ThemeBase.LightGreyFillColor2);

            bmpLoopNone    = g.CreateBitmapFromResource("LoopNone");
            bmpLoopSong    = g.CreateBitmapFromResource("Loop");
            bmpLoopPattern = g.CreateBitmapFromResource("LoopPattern");
            bmpPlay        = g.CreateBitmapFromResource("Play");
            bmpPause       = g.CreateBitmapFromResource("Pause");

            buttons[ButtonNew] = new Button {
                X = 4, Y = 4, Bmp = g.CreateBitmapFromResource("File"), Click = OnNew
            };
            buttons[ButtonOpen] = new Button {
                X = 44, Y = 4, Bmp = g.CreateBitmapFromResource("Open"), Click = OnOpen
            };
            buttons[ButtonSave] = new Button {
                X = 84, Y = 4, Bmp = g.CreateBitmapFromResource("Save"), Click = OnSave, RightClick = OnSaveAs
            };
            buttons[ButtonExport] = new Button {
                X = 124, Y = 4, Bmp = g.CreateBitmapFromResource("Export"), Click = OnExport
            };
            buttons[ButtonCopy] = new Button {
                X = 164, Y = 4, Bmp = g.CreateBitmapFromResource("Copy"), Click = OnCopy, Enabled = OnCopyEnabled
            };
            buttons[ButtonCut] = new Button {
                X = 204, Y = 4, Bmp = g.CreateBitmapFromResource("Cut"), Click = OnCut, Enabled = OnCutEnabled
            };
            buttons[ButtonPaste] = new Button {
                X = 244, Y = 4, Bmp = g.CreateBitmapFromResource("Paste"), Click = OnPaste, RightClick = OnPasteSpecial, Enabled = OnPasteEnabled
            };
            buttons[ButtonUndo] = new Button {
                X = 284, Y = 4, Bmp = g.CreateBitmapFromResource("Undo"), Click = OnUndo, Enabled = OnUndoEnabled
            };
            buttons[ButtonRedo] = new Button {
                X = 324, Y = 4, Bmp = g.CreateBitmapFromResource("Redo"), Click = OnRedo, Enabled = OnRedoEnabled
            };
            buttons[ButtonConfig] = new Button {
                X = 364, Y = 4, Bmp = g.CreateBitmapFromResource("Config"), Click = OnConfig
            };
            buttons[ButtonPlay] = new Button {
                X = 594, Y = 4, Click = OnPlay, GetBitmap = OnPlayGetBitmap
            };
            buttons[ButtonRewind] = new Button {
                X = 634, Y = 4, Bmp = g.CreateBitmapFromResource("Rewind"), Click = OnRewind
            };
            buttons[ButtonLoop] = new Button {
                X = 674, Y = 4, Click = OnLoop, GetBitmap = OnLoopGetBitmap
            };

            buttons[ButtonNew].ToolTip    = "{MouseLeft} New Project {Ctrl} {N}";
            buttons[ButtonOpen].ToolTip   = "{MouseLeft} Open Project {Ctrl} {O}";
            buttons[ButtonSave].ToolTip   = "{MouseLeft} Save Project {Ctrl} {S}  - {MouseRight} Save As...";
            buttons[ButtonExport].ToolTip = "{MouseLeft} Export to various formats {Ctrl} {E}";
            buttons[ButtonCopy].ToolTip   = "{MouseLeft} Copy selection {Ctrl} {C}";
            buttons[ButtonCut].ToolTip    = "{MouseLeft} Cut selection {Ctrl} {X}";
            buttons[ButtonPaste].ToolTip  = "{MouseLeft} Paste {Ctrl} {V}\n{MouseRight} Paste Special... {Ctrl} {Shift} {V}";
            buttons[ButtonUndo].ToolTip   = "{MouseLeft} Undo {Ctrl} {Z}";
            buttons[ButtonRedo].ToolTip   = "{MouseLeft} Redo {Ctrl} {Y}";
            buttons[ButtonConfig].ToolTip = "{MouseLeft} Edit Application Settings";
            buttons[ButtonPlay].ToolTip   = "{MouseLeft} Play/Pause {Space}\nPlay pattern loop {Ctrl} {Space}  - Play song loop {Shift} {Space}";
            buttons[ButtonRewind].ToolTip = "{MouseLeft} Rewind {Home}\nRewind to beginning of current pattern {Ctrl} {Home}";
            buttons[ButtonLoop].ToolTip   = "{MouseLeft} Toggle Loop Mode";

            var scaling = RenderTheme.MainWindowScaling;

            for (int i = 0; i < ButtonCount; i++)
            {
                var btn = buttons[i];
                btn.X    = (int)(btn.X * scaling);
                btn.Y    = (int)(btn.Y * scaling);
                btn.Size = (int)(btn.Size * scaling);
            }

            timecodePosX            = (int)(DefaultTimecodePosX * scaling);
            timecodePosY            = (int)(DefaultTimecodePosY * scaling);
            timecodeSizeX           = (int)(DefaultTimecodeSizeX * scaling);
            timecodeTextPosX        = (int)(DefaultTimecodeTextPosX * scaling);
            tooltipSingleLinePosY   = (int)(DefaultTooltipSingleLinePosY * scaling);
            tooltipMultiLinePosY    = (int)(DefaultTooltipMultiLinePosY * scaling);
            tooltipLineSizeY        = (int)(DefaultTooltipLineSizeY * scaling);
            tooltipSpecialCharSizeX = (int)(DefaultTooltipSpecialCharSizeX * scaling);
            tooltipSpecialCharSizeY = (int)(DefaultTooltipSpecialCharSizeY * scaling);

            specialCharacters["Shift"] = new TooltipSpecialCharacter {
                Width = (int)(32 * scaling)
            };
            specialCharacters["Space"] = new TooltipSpecialCharacter {
                Width = (int)(38 * scaling)
            };
            specialCharacters["Home"] = new TooltipSpecialCharacter {
                Width = (int)(38 * scaling)
            };
            specialCharacters["Ctrl"] = new TooltipSpecialCharacter {
                Width = (int)(28 * scaling)
            };
            specialCharacters["Alt"] = new TooltipSpecialCharacter {
                Width = (int)(24 * scaling)
            };
            specialCharacters["Drag"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("Drag"), OffsetY = 2 * scaling
            };
            specialCharacters["MouseLeft"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("MouseLeft"), OffsetY = 2 * scaling
            };
            specialCharacters["MouseRight"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("MouseRight"), OffsetY = 2 * scaling
            };
            specialCharacters["MouseWheel"] = new TooltipSpecialCharacter {
                Bmp = g.CreateBitmapFromResource("MouseWheel"), OffsetY = 2 * scaling
            };

            for (char i = 'A'; i <= 'Z'; i++)
            {
                specialCharacters[i.ToString()] = new TooltipSpecialCharacter {
                    Width = tooltipSpecialCharSizeX
                }
            }
            ;

            foreach (var specialChar in specialCharacters.Values)
            {
                if (specialChar.Bmp != null)
                {
                    specialChar.Width = (int)g.GetBitmapWidth(specialChar.Bmp);
                }
                specialChar.Height = tooltipSpecialCharSizeY;
            }
        }