internal PlayerModel(
            int playerIndex,
            int asioChannel,
            HolofunkModel holofunkModel)
        {
            m_playerIndex = playerIndex;
            m_asioChannel = asioChannel;

            m_parent = holofunkModel;

            // TODO: EVIL INIT ORDER DEPENDENCY: hand models contain state machines, which update scene graphs on initialization!
            m_playerSceneGraph = new PlayerSceneGraph(holofunkModel.SceneGraph, playerIndex, asioChannel);

            m_leftHandModel = new PlayerHandModel(this, false);
            m_rightHandModel = new PlayerHandModel(this, true);

            // the microphone has only per-loop parameters
            m_microphoneParameters = AllEffects.CreateParameterMap();
        }
        internal void Update(
            PlayerHandModel playerModel,
            HolofunKinect kinect,
            Moment now)
        {
            m_parent.Update(playerModel, kinect, now);

            bool isDragging = m_model.DragStartLocation.HasValue;
            Color color = isDragging ? Color.White : new Color(0, 0, 0, 0);

            // cut alpha of bounding circle
            m_boundingCircleNode.Color = color * 0.5f;
            m_effectKnobLineNode.Color = color;
            m_effectKnobNode.Color = color;

            if (isDragging) {
                m_boundingCircleNode.LocalTransform = new Transform(m_model.DragStartLocation.Value, m_boundingCircleNode.LocalTransform.Scale);
                m_effectKnobLineNode.SetEndpoints(m_model.DragStartLocation.Value, m_model.CurrentKnobLocation.Value);
                m_effectKnobNode.LocalTransform = new Transform(m_model.CurrentKnobLocation.Value, m_effectKnobNode.LocalTransform.Scale);

                // TODO: this is a bit inefficient and hacky
                m_parent.ShowEffectLabels(PlayerEffectSpaceModel.EffectSettings[playerModel.EffectPresetIndex], now);
            }
        }
        internal PlayerEffectSpaceModel(PlayerHandModel playerHandModel)
        {
            m_parent = playerHandModel;
            m_effectSettings = EffectSettings[playerHandModel.EffectPresetIndex];

            m_sceneGraph = new PlayerEffectSpaceSceneGraph(m_parent.SceneGraph, this);

            // m_parent.SceneGraph.SetEffectLabelNode(m_sceneGraph.BoundingCircleNode);

            m_microphoneSelected = playerHandModel.OtherArmPose == ArmPose.AtMouth;
        }
        internal void Update(PlayerHandModel playerHandModel, HolofunKinect kinect, Moment now)
        {
            // The position adjustment here is purely ad hoc -- the depth image still
            // doesn't line up well with the skeleton-to-depth-mapped hand positions.
            m_handGroup.LocalTransform = new Transform(
                kinect.GetJointViewportPosition(
                    playerHandModel.PlayerModel.PlayerIndex,
                    playerHandModel.IsRightHand ? JointType.HandRight : JointType.HandLeft) + MagicNumbers.ScreenHandAdjustment,
                new Vector2(MagicNumbers.LoopieScale));

            // and make the mike signal update appropriately
            m_handMikeSignal.Update(now, false, playerHandModel.PlayerModel.PlayerColor);

            if (m_effectLabelShownMoment.HasValue) {
                Duration<Sample> elapsed = now.Time - m_effectLabelShownMoment.Value.Time;
                Color color = new Color(0, 0, 0, 0);
                if (elapsed > MagicNumbers.EffectLabelFadeDuration) {
                    m_effectLabelShownMoment = Option<Moment>.None;
                }
                else {
                    float fraction = 1f - ((float)(long)elapsed / MagicNumbers.EffectLabelFadeDuration);
                    color = Alpha(fraction);
                }
                m_effectLabels[0].Color = color;
                m_effectLabels[1].Color = color;
                m_effectLabels[2].Color = color;
                m_effectLabels[3].Color = color;
            }

            // Debugging elbow arm pose label.
            ArmPose armPose = kinect.GetArmPose(m_parent.PlayerIndex, m_isRight ? Side.Right : Side.Left);
            m_armPoseLabel.Text.Clear();
            m_armPoseLabel.Text.Append(
                armPose == ArmPose.AtChest ? "Chest"
                : armPose == ArmPose.AtMouth ? "Mouth"
                : armPose == ArmPose.OnHead ? "Head"
                : "");
            m_armPoseLabel.LocalTransform = new Transform(
                kinect.GetJointViewportPosition(m_parent.PlayerIndex, m_isRight ? JointType.HandRight : JointType.HandLeft)
                    + new Vector2(0, 50),
                new Vector2(0.7f));
        }