private ArrayList getNoteDifficulty()
        {
            ArrayList[] note_stack       = { new ArrayList(), new ArrayList() };
            Note[]      prior_color_note = { null, null };
            ArrayList   result_note_data = new ArrayList();

            // not used for anything just yet
            ArrayList prior_note_stack = new ArrayList();

            for (int itr = 0; itr < _notes.Length; itr++)
            {
                Note note = _notes[itr];
                // keep a history of previous notes
                if (note._type != 2)
                {
                    prior_note_stack.Add(note);
                }

                if (note._type == 0 || note._type == 1)
                {
                    float score     = 0.0f;
                    Note  next_note = getNextNote(_notes, note._type, itr);
                    if (next_note != null)
                    {
                        if (note._time == next_note._time)
                        {
                            //red_note_stack.push(note);
                            note_stack[note._type].Add(note);
                            continue;
                        }
                    }
                    if (note_stack[note._type].Count > 0)
                    {
                        Note last_of_type = (Note)note_stack[note._type][note_stack[note._type].Count - 1];
                        if (note._time == last_of_type._time)
                        { // don't handle stack yet, may be more notes
                            note_stack[note._type].Add(note);
                            //red_note_stack.push(note);
                            if (getNextNote(_notes, note._type, itr) != null)
                            {
                                continue;
                            }
                        }
                        // handle stack here
                        if (note_stack[note._type].Count == 2)
                        {
                            // handle two note stack here
                            // figure out which note is first, based on distance. Really need to know the last note
                            Note note_a = (Note)note_stack[note._type][0], note_b = (Note)note_stack[note._type][1], first_note = null, last_note = null;
                            if (GetPhysicalNoteDistance(note_a, note_b) < GetPhysicalNoteDistance(note_b, note_a))
                            {
                                first_note = note_a; last_note = note_b;
                            }
                            else
                            {
                                first_note = note_b; last_note = note_a;
                            }

                            // add the average to each note to give a difficulty boost
                            float score_a = NoteHitDifficulty(prior_note_stack, prior_color_note[note._type], first_note), score_b = NoteHitDifficulty(prior_note_stack, prior_color_note[note._type], last_note);
                            float avg_score_a_b = ((score_a + score_b) / 2.0f);
                            score_a += avg_score_a_b;
                            score_b += avg_score_a_b;

                            NoteDiff a = new NoteDiff()
                            {
                                _time = note_a._time, _diffScore = score_a, _type = note_a._type
                            };
                            NoteDiff b = new NoteDiff()
                            {
                                _time = note_b._time, _diffScore = score_b, _type = note_b._type
                            };
                            result_note_data.Add(a);
                            result_note_data.Add(b);
                            prior_color_note[note._type] = last_note;
                        }
                        else
                        {
                            ArrayList dist_data     = new ArrayList();
                            float     largest_dist  = -1;
                            int       largest_index = 0;
                            ArrayList diffs         = new ArrayList();

                            for (int i = 0; i < note_stack[note._type].Count; i++)
                            {
                                for (int j = 0; j < note_stack[note._type].Count; j++)
                                {
                                    Note note_a = (Note)note_stack[note._type][i];
                                    Note note_b = (Note)note_stack[note._type][j];
                                    if (i != j)
                                    {
                                        var dist = GetPhysicalNoteDistance(note_a, note_b);
                                        if (dist >= largest_dist)
                                        {
                                            largest_dist  = dist;
                                            largest_index = i;
                                        }
                                    }
                                }
                                diffs.Add(NoteHitDifficulty(prior_note_stack, prior_color_note[note._type], note));
                            }
                            for (int i = 0; i < note_stack[note._type].Count; i++)
                            {
                                for (int j = 0; j < note_stack[note._type].Count; j++)
                                {
                                    Note note_a = (Note)note_stack[note._type][i];
                                    Note note_b = (Note)note_stack[note._type][j];
                                    if (i != j)
                                    {
                                        var dist = GetPhysicalNoteDistance(note_a, note_b);
                                        if (dist >= largest_dist)
                                        {
                                            largest_dist  = dist;
                                            largest_index = i;
                                        }
                                    }
                                }
                                diffs.Add(NoteHitDifficulty(prior_note_stack, prior_color_note[note._type], note));
                            }
                            Note  last_note = (Note)note_stack[note._type][largest_index];
                            float sum       = 0;
                            foreach (float diff in diffs)
                            {
                                sum += diff;
                            }
                            float sum_avg = sum / (float)(diffs.Count);
                            for (int i = 0; i < diffs.Count; i++)
                            {
                                if (i >= note_stack[note._type].Count)
                                {
                                    continue;
                                }
                                Note     note_a = (Note)note_stack[note._type][i];
                                float    diff   = (float)diffs[i];
                                NoteDiff a      = new NoteDiff()
                                {
                                    _time = note_a._time, _diffScore = diff + sum_avg, _type = note_a._type
                                };
                                result_note_data.Add(a);
                            }
                            prior_color_note[note._type] = last_note;
                        }
                        note_stack[note._type].Clear();
                    }
                    if (note_stack[note._type].Count == 0)
                    {
                        score = NoteHitDifficulty(prior_note_stack, prior_color_note[note._type], note);
                        prior_color_note[note._type] = note;
                        NoteDiff nd = new NoteDiff()
                        {
                            _time = note._time, _diffScore = score, _type = note._type
                        };
                        result_note_data.Add(nd);
                    }
                }
                else
                {
                    // skip because this is a bomb or wall ...
                }
                // pop off old notes more than a beat away
                Note temp = (Note)prior_note_stack[0];
                while (prior_note_stack.Count > 0 && temp._time < note._time - 1)
                {
                    prior_note_stack.RemoveAt(0);
                }
            }
            //return { 'red_diffs': red_diffs, 'blue_diffs': blue_diffs };
            writer.Write("Calculated Note Difficulty for " + _notes.Length + " notes." + Environment.NewLine);
            return(result_note_data);
        }
        private float calcSongEnduranceScore(ArrayList note_data, int beats_per_section)
        {
            // first find peak strain value
            NoteDiff first_note  = (NoteDiff)note_data[0];
            NoteDiff last_note   = (NoteDiff)note_data[note_data.Count - 1];
            float    first_beat  = first_note._time;
            float    last_beat   = last_note._time;
            float    peak_strain = -1f;
            //float curr_strain = 0;
            ArrayList strains = new ArrayList(); // init all possible beat sections to 0

            for (int itr = (int)Math.Floor(first_beat / beats_per_section) * beats_per_section; itr <= (int)Math.Floor(last_beat / beats_per_section) * beats_per_section; itr += beats_per_section)
            {
                //strains[itr / beats_per_section] = 0;
                strains.Add(0.0f);
            }
            float curr_beat = first_beat;

            foreach (NoteDiff note in note_data)
            {
                int beat_pos = (int)Math.Floor(note._time / beats_per_section);
                if (beat_pos < strains.Count)
                {
                    strains[beat_pos] = (float)(strains[beat_pos]) + note._diffScore;
                }
                else
                {
                    strains.Add(note._diffScore);
                }
            }

            foreach (float strain in strains)
            {
                if (strain > peak_strain)
                {
                    peak_strain = strain;
                }
            }

            // analyze the strain over time
            // first find average
            float strain_avg = 0f, strain_sum = 0f;

            foreach (float strain in strains)
            {
                strain_sum += strain;
            }
            strain_avg = strain_sum / (float)(strains.Count);

            // next std deviation
            float strain_dev = 0;

            strain_sum = 0;
            foreach (float strain in strains)
            {
                strain_sum += (float)(Math.Pow(strain - strain_avg, 2.0f));
            }
            strain_dev = (float)Math.Sqrt(strain_sum / ((float)strains.Count));

            writer.Write("Strain Peak: " + peak_strain + " Strain Avg: " + strain_avg + " Strain Dev " + strain_dev + Environment.NewLine);

            // do some bullshit math to get score
            float strain_score = 0f;
            float bonus        = 0.0f; // the longer we are close to or above avg by std, then increase bonus because is tiring to work above avg all the time... something like that?
            float max_bonus    = 2.0f;

            for (int i = 0; i < strains.Count; i++)
            {
                float strain = (float)strains[i];
                float score  = 0;
                if (strain > strain_avg)
                {
                    if (strain > strain_avg + strain_dev * 3f)
                    {
                        bonus = Math.Min(bonus + 0.3f, max_bonus);
                    }
                    else if (strain > strain_avg + strain_dev * 2f)
                    {
                        bonus = Math.Min(bonus + 0.11f, max_bonus);
                    }
                    else if (strain > strain_avg + strain_dev * 1f)
                    {
                        bonus = Math.Min(bonus + 0.05f, max_bonus);
                    }
                    else if (strain > strain_avg + strain_dev * 0.5f)
                    {
                        bonus = Math.Min(bonus + 0.03f, max_bonus); // new
                    }
                    else if (strain > strain_avg + strain_dev * 0f)
                    {
                        bonus = Math.Min(bonus + 0.02f, max_bonus); // was 0.03
                    }
                }
                else
                {
                    if (strain < strain_avg - strain_dev * 3f)
                    {
                        bonus = Math.Max(bonus - 0.20f, 0.0f);
                    }
                    else if (strain < strain_avg - strain_dev * 2f)
                    {
                        bonus = Math.Max(bonus - 0.10f, 0.0f);
                    }
                    else if (strain < strain_avg - strain_dev * 1f)
                    {
                        bonus = Math.Max(bonus - 0.05f, 0.0f);
                    }
                    else if (strain < strain_avg - strain_dev * 0.5f)
                    {
                        bonus = Math.Max(bonus - 0.01f, max_bonus);
                    }
                    else if (strain < strain_avg - strain_dev * 0f)
                    {
                        bonus = Math.Max(bonus + 0.002f, max_bonus);
                    }

                    // trying out having strain for a section not compared against peak strain
                    // TODO tried it, need more balancing for sure, helped some songs, made many more worse
                    score += strain * (1.0f + bonus);
                    //score += (float)Math.Pow(strain / peak_strain, 2f) * (1f + bonus);
                }
                score *= 1f + ((float)i / (float)strains.Count) * 0.3f;

                // TODO Balance
                //writer.Write("Strain Pre Length: " + score + Environment.NewLine);
                //score *= 1.0f + ((float)i / (25.0f * (float)beats_per_section));
                //writer.Write("Strain Post Length: " + score + Environment.NewLine);

                strain_score += score;
            }

            strain_score *= (peak_strain / 3f) * (strain_avg / 2f) * Mathf.Clamp(10f / strain_dev, 0.6f, 2.0f); // bonus scalar so easy songs that are uniform in diff don't get high strain scores
            writer.Write("Strain Score Pre Log: " + strain_score + Environment.NewLine);
            //strain_score = (float)(Math.Log(strain_score) / Math.Log(1.2));
            strain_score = (float)Math.Sqrt(strain_score);
            // This should never happen anymore, but leaving just in case
            if (strain_score < 0f)
            {
                strain_score = 0f;
            }
            writer.Write("Strain Score For " + beats_per_section + " Beats: " + strain_score + Environment.NewLine);
            return(strain_score);
        }