public void GroupSelectedBlocks()
        {
            if (!SelectedBlocks.Any())
            {
                return;
            }

            var          relatedSegments = Enumerable.Select(SelectedBlocks, b => b.SegmentContext).Distinct().ToList();
            MusicSegment segmentForGroup = (relatedSegments.Count == 1 ? relatedSegments.Single().GetModel() : model.MusicSegments[0]);

            var group = new LoopBlock(model);

            group.SegmentContext = segmentForGroup;
            group.StartTime      = SelectedBlocks.Min(b => b.StartTime);
            foreach (var b in SelectedBlocks)
            {
                // create an independent copy of the block so transforming its time to local reference frame does not screw up undo
                Block newChild = Block.FromXML(model, b.GetModel().ToXML());

                group.AddChild(newChild, true);
            }

            using (ActionManager.CreateTransaction())
            {
                DeleteSelectedBlocks();
                ActionManager.RecordAdd(model.Blocks, group);
            }

            SelectBlock(BlockViewModel.FromModel(this, group), CompositionMode.None);
        }
        // type may be null, then the target will be auto-inferred based on currently selected blocks
        public void ConvertSelectedBlocksTo(string type)
        {
            bool rampsToColors = (type == "color");
            bool colorsToRamps = (type == "ramp");

            if (!rampsToColors && !colorsToRamps)
            {
                colorsToRamps = SelectedBlocks.Any(b => b is ColorBlockViewModel);
                rampsToColors = SelectedBlocks.Any(b => b is RampBlockViewModel);
                if (colorsToRamps && rampsToColors)
                {
                    throw new ArgumentException("type needs to be given when both color and ramp blocks are selected");
                }
            }

            using (ActionManager.CreateTransaction())
            {
                for (int i = 0; i < SelectedBlocks.Count; i++)
                {
                    var   block = SelectedBlocks[i].GetModel();
                    Block convertedBlock;
                    if (rampsToColors && block is RampBlock rampBlock)
                    {
                        convertedBlock = new ColorBlock(model, rampBlock.Tracks.ToArray())
                        {
                            Color = (rampBlock.StartColor == GloColor.Black ? rampBlock.EndColor : rampBlock.StartColor)
                        };
                    }
                    else if (colorsToRamps && block is ColorBlock colorBlock)
                    {
                        convertedBlock = new RampBlock(model, colorBlock.Tracks.ToArray())
                        {
                            StartColor = colorBlock.Color,
                            EndColor   = (colorBlock.Color == GloColor.White ? GloColor.Black : GloColor.White)
                        };
                    }
                    else
                    {
                        continue;
                    }

                    // copy generic properties
                    convertedBlock.StartTime      = block.StartTime;
                    convertedBlock.Duration       = block.Duration;
                    convertedBlock.SegmentContext = block.SegmentContext;

                    ActionManager.RecordReplace(model.Blocks, block, convertedBlock);
                    SelectedBlocks[i] = BlockViewModel.FromModel(this, convertedBlock);
                }
            }
        }