// Voice.cpp // Copyright (c) 1996-2000 Microsoft Corporation. All Rights Reserved. // #include "common.h" #include #include "muldiv32.h" #define STR_MODULENAME "DDKSynth.sys:Voice: " #ifdef _X86_ #define MMX_ENABLED 1 #endif #pragma code_seg() /***************************************************************************** * CVoiceLFO::CVoiceLFO() ***************************************************************************** * Constructor for the low-frequency oscillator object. */ CVoiceLFO::CVoiceLFO() { m_pModWheelIn = NULL; } /***************************************************************************** * snSineTable[] ***************************************************************************** * Table of 16-bit integers, representing a sine wave. * value = sin((index*6.283185307)/256) * 100 where index = 0..255 */ const CHAR snSineTable[] = { // 0 1 2 3 4 5 6 7 0, 2, 4, 7, 9, 12, 14, 17, 19, 21, 24, 26, 29, 31, 33, 35, 38, 40, 42, 44, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 68, 70, 72, 74, 75, 77, 78, 80, 81, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 94, 95, 96, 97, 97, 98, 98, 98, 99, 99, 99, 99, 99, 100, 99, 99, 99, 99, 99, 98, 98, 98, 97, 97, 96, 95, 94, 94, 93, 92, 91, 90, 89, 88, 87, 85, 84, 83, 81, 80, 78, 77, 75, 74, 72, 70, 68, 67, 65, 63, 61, 59, 57, 55, 53, 51, 49, 47, 44, 42, 40, 38, 35, 33, 31, 29, 26, 24, 21, 19, 17, 14, 12, 9, 7, 4, 2, 0, -2, -4, -7, -9, -12, -14, -17, -19, -21, -24, -26, -29, -31, -33, -35, -38, -40, -42, -44, -47, -49, -51, -53, -55, -57, -59, -61, -63, -65, -67, -68, -70, -72, -74, -75, -77, -78, -80, -81, -83, -84, -85, -87, -88, -89, -90, -91, -92, -93, -94, -94, -95, -96, -97, -97, -98, -98, -98, -99, -99, -99, -99, -99, -100, -99, -99, -99, -99, -99, -98, -98, -98, -97, -97, -96, -95, -94, -94, -93, -92, -91, -90, -89, -88, -87, -85, -84, -83, -81, -80, -78, -77, -75, -74, -72, -70, -68, -67, -65, -63, -61, -59, -57, -55, -53, -51, -49, -47, -44, -42, -40, -38, -35, -33, -31, -29, -26, -24, -21, -19, -17, -14, -12, -9, -7, -4, -2 }; /***************************************************************************** * CVoiceLFO::StartVoice() ***************************************************************************** * Start a voice with this LFO. Attach the given ModWheel receptor. */ STIME CVoiceLFO::StartVoice(CSourceLFO *pSource,STIME stStartTime, CModWheelIn * pModWheelIn) { m_pModWheelIn = pModWheelIn; m_Source = *pSource; m_stStartTime = stStartTime; if ((m_Source.m_prMWPitchScale == 0) && (m_Source.m_vrMWVolumeScale == 0) && (m_Source.m_prPitchScale == 0) && (m_Source.m_vrVolumeScale == 0)) { m_stRepeatTime = 44100; } else { m_stRepeatTime = 2097152 / m_Source.m_pfFrequency; // (1/8 * 256 * 4096 * 16) } return (m_stRepeatTime); } /***************************************************************************** * CVoiceLFO::GetLevel() ***************************************************************************** * Return the value of the LFO right now. */ long CVoiceLFO::GetLevel(STIME stTime, STIME *pstNextTime) { stTime -= (m_stStartTime + m_Source.m_stDelay); if (stTime < 0) { *pstNextTime = -stTime; return (0); } *pstNextTime = m_stRepeatTime; stTime *= m_Source.m_pfFrequency; stTime = stTime >> (12 + 4); // We've added 4 extra bits of resolution... return (::snSineTable[stTime & 0xFF]); } /***************************************************************************** * CVoiceLFO::GetVolume() ***************************************************************************** * Get the composite volume of the LFO. */ VREL CVoiceLFO::GetVolume(STIME stTime, STIME *pstNextTime) { VREL vrVolume = m_pModWheelIn->GetModulation(stTime); vrVolume *= m_Source.m_vrMWVolumeScale; vrVolume /= 127; vrVolume += m_Source.m_vrVolumeScale; vrVolume *= GetLevel(stTime,pstNextTime); vrVolume /= 100; return (vrVolume); } /***************************************************************************** * CVoiceLFO::GetPitch() ***************************************************************************** * Get the composite pitch of the LFO. */ PREL CVoiceLFO::GetPitch(STIME stTime, STIME *pstNextTime) { PREL prPitch = m_pModWheelIn->GetModulation(stTime); prPitch *= m_Source.m_prMWPitchScale; prPitch /= 127; prPitch += m_Source.m_prPitchScale; prPitch *= GetLevel(stTime,pstNextTime); prPitch /= 100; return (prPitch); } /***************************************************************************** * CVoiceEG::CVoiceEG() ***************************************************************************** * Constructor for the CVoiceEG object. */ CVoiceEG::CVoiceEG() { m_stStopTime = 0; } /***************************************************************************** * snAttackTable[] ***************************************************************************** * Table of 16-bit integers, representing a log attack curve. * value = (log10((index/200)^2)) * 10000 / 96 + 1000 where index = 0..199 */ const short snAttackTable[] = { // 0 1 2 3 4 5 6 7 0, 520, 583, 620, 646, 666, 682, 696, 708, 719, 728, 737, 745, 752, 759, 765, 771, 776, 782, 787, 791, 796, 800, 804, 808, 811, 815, 818, 822, 825, 828, 831, 834, 836, 839, 842, 844, 847, 849, 852, 854, 856, 858, 860, 863, 865, 867, 868, 870, 872, 874, 876, 878, 879, 881, 883, 884, 886, 887, 889, 891, 892, 894, 895, 896, 898, 899, 901, 902, 903, 905, 906, 907, 908, 910, 911, 912, 913, 914, 915, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 939, 940, 941, 942, 943, 944, 945, 945, 946, 947, 948, 949, 949, 950, 951, 952, 953, 953, 954, 955, 956, 956, 957, 958, 958, 959, 960, 961, 961, 962, 963, 963, 964, 965, 965, 966, 967, 967, 968, 969, 969, 970, 970, 971, 972, 972, 973, 973, 974, 975, 975, 976, 976, 977, 978, 978, 979, 979, 980, 980, 981, 982, 982, 983, 983, 984, 984, 985, 985, 986, 986, 987, 987, 988, 988, 989, 989, 990, 990, 991, 991, 992, 992, 993, 993, 994, 994, 995, 995, 996, 996, 997, 997, 998, 998, 999, 999, 1000 }; /***************************************************************************** * CVoiceEG::StopVoice() ***************************************************************************** * Stop the envelope generator. Use a heuristic to hasten the cutoff, * depending on the current level. */ void CVoiceEG::StopVoice(STIME stTime) { m_Source.m_stRelease *= GetLevel(stTime,&m_stStopTime,TRUE); // Adjust for current sustain level. m_Source.m_stRelease /= 1000; m_stStopTime = stTime; } /***************************************************************************** * CVoiceEG::QuickStopVoice() ***************************************************************************** * Stop the envelope generator ASAP. */ void CVoiceEG::QuickStopVoice(STIME stTime, DWORD dwSampleRate) { m_Source.m_stRelease *= GetLevel(stTime,&m_stStopTime,TRUE); // Adjust for current sustain level. m_Source.m_stRelease /= 1000; dwSampleRate /= 70; if (m_Source.m_stRelease > (long) dwSampleRate) { m_Source.m_stRelease = dwSampleRate; } m_stStopTime = stTime; } /***************************************************************************** * CVoiceEG::StartVoice() ***************************************************************************** * Start the voice with the given envelope generator and parameters. */ STIME CVoiceEG::StartVoice(CSourceEG *pSource, STIME stStartTime, WORD nKey, WORD nVelocity) { m_stStartTime = stStartTime; m_stStopTime = 0x7fffffffffffffff; // set to indefinite future m_Source = *pSource; // apply velocity to attack length scaling here m_Source.m_stAttack *= CDigitalAudio::PRELToPFRACT(nVelocity * m_Source.m_trVelAttackScale / 127); m_Source.m_stAttack /= 4096; m_Source.m_stDecay *= CDigitalAudio::PRELToPFRACT(nKey * m_Source.m_trKeyDecayScale / 127); m_Source.m_stDecay /= 4096; m_Source.m_stDecay *= (1000 - m_Source.m_pcSustain); m_Source.m_stDecay /= 1000; return ((STIME)m_Source.m_stAttack); } /***************************************************************************** * CVoiceEG::InAttack() ***************************************************************************** * Are we in the attack phase still? */ BOOL CVoiceEG::InAttack(STIME st) { // has note been released? if (st >= m_stStopTime) return FALSE; // past length of attack? if (st >= m_stStartTime + m_Source.m_stAttack) return FALSE; return TRUE; } /***************************************************************************** * CVoiceEG::InRelease() ***************************************************************************** * Are we in the release phase yet? */ BOOL CVoiceEG::InRelease(STIME st) { // has note been released? if (st > m_stStopTime) return TRUE; return FALSE; } /***************************************************************************** * CVoiceEG::GetLevel() ***************************************************************************** * Get the envelope generator's current level, from 0 to 1000. */ long CVoiceEG::GetLevel(STIME stEnd, STIME *pstNext, BOOL fVolume) { long lLevel = 0; if (stEnd <= m_stStopTime) { stEnd -= m_stStartTime; // note not released yet. if (stEnd < m_Source.m_stAttack) { // still in attack lLevel = 1000 * (long) stEnd; if (m_Source.m_stAttack) { lLevel /= (long) m_Source.m_stAttack; } else // This should never happen, but it does... { lLevel = 0; } *pstNext = m_Source.m_stAttack - stEnd; if (lLevel < 0) lLevel = 0; if (lLevel > 1000) lLevel = 1000; if (fVolume) { lLevel = ::snAttackTable[lLevel / 5]; } } else { stEnd -= m_Source.m_stAttack; if (stEnd < m_Source.m_stDecay) { // still in decay lLevel = (1000 - m_Source.m_pcSustain) * (long) stEnd; lLevel /= (long) m_Source.m_stDecay; lLevel = 1000 - lLevel; // To improve the decay curve, set the next point to be 1/4, 1/2, or end of slope. // To avoid close duplicates, fudge an extra 100 samples. if (stEnd < ((m_Source.m_stDecay >> 2) - 100)) { *pstNext = (m_Source.m_stDecay >> 2) - stEnd; } else if (stEnd < ((m_Source.m_stDecay >> 1) - 100)) { *pstNext = (m_Source.m_stDecay >> 1) - stEnd; } else { *pstNext = m_Source.m_stDecay - stEnd; // Next is end of decay. } } else { // in sustain lLevel = m_Source.m_pcSustain; *pstNext = 44100; } } } else { STIME stBogus; // in release stEnd -= m_stStopTime; if (stEnd < m_Source.m_stRelease) { lLevel = GetLevel(m_stStopTime,&stBogus,fVolume) * (long) (m_Source.m_stRelease - stEnd); lLevel /= (long) m_Source.m_stRelease; if (stEnd < ((m_Source.m_stRelease >> 2) - 100)) { *pstNext = (m_Source.m_stRelease >> 2) - stEnd; } else if (stEnd < ((m_Source.m_stRelease >> 1) - 100)) { *pstNext = (m_Source.m_stRelease >> 1) - stEnd; } else { *pstNext = m_Source.m_stRelease - stEnd; // Next is end of decay. } } else { lLevel = 0; // !!! off *pstNext = 0x7FFFFFFFFFFFFFFF; } } return lLevel; } /***************************************************************************** * CVoiceEG::GetVolume() ***************************************************************************** * Get the composite volume of the envelope generator in dB cents (1/100ths db). */ VREL CVoiceEG::GetVolume(STIME stTime, STIME *pstNextTime) { VREL vrLevel = GetLevel(stTime, pstNextTime, TRUE) * 96; vrLevel /= 10; vrLevel = vrLevel - 9600; return vrLevel; } /***************************************************************************** * CVoiceEG::GetPitch() ***************************************************************************** * Get the composite pitch of the envelope generator, in fractional scale units. */ PREL CVoiceEG::GetPitch(STIME stTime, STIME *pstNextTime) { PREL prLevel; if (m_Source.m_sScale != 0) { prLevel = GetLevel(stTime, pstNextTime,FALSE); prLevel *= m_Source.m_sScale; prLevel /= 1000; } else { *pstNextTime = 44100; prLevel = 0; } return prLevel; } BOOL MultiMediaInstructionsSupported(); /***************************************************************************** * CDigitalAudio::CDigitalAudio() ***************************************************************************** * Initialize the digital audio object. * This object manages the sample looping and playback and * digitally controlled amplifier. */ CDigitalAudio::CDigitalAudio() { m_pfBasePitch = 0; m_pfLastPitch = 0; m_pfLastSample = 0; m_pfLoopEnd = 0; m_pfLoopStart = 0; m_pfSampleLength = 0; m_prLastPitch = 0; m_vrLastLVolume = 0; m_vrLastRVolume = 0; m_vrBaseLVolume = 0; m_vrBaseRVolume = 0; m_vfLastLVolume = 0; m_vfLastRVolume = 0; m_ullLastSample = 0; m_ullLoopStart = 0; m_ullLoopEnd = 0; m_ullSampleLength = 0; m_fElGrande = FALSE; #ifdef MMX_ENABLED m_fMMXEnabled = MultiMediaInstructionsSupported(); #endif // MMX_ENABLED }; /***************************************************************************** * Other CDigitalAudio tables *****************************************************************************/ // Pitch increment lookup. // value = ((index/1200)^2)*4096 where index = -100..100 const /*PFRACT*/SHORT pfCents[] = { // 0 1 2 3 4 5 6 7 3866, 3868, 3870, 3872, 3875, 3877, 3879, 3881, 3884, 3886, 3888, 3890, 3893, 3895, 3897, 3899, 3902, 3904, 3906, 3908, 3911, 3913, 3915, 3917, 3920, 3922, 3924, 3926, 3929, 3931, 3933, 3935, 3938, 3940, 3942, 3945, 3947, 3949, 3951, 3954, 3956, 3958, 3961, 3963, 3965, 3967, 3970, 3972, 3974, 3977, 3979, 3981, 3983, 3986, 3988, 3990, 3993, 3995, 3997, 4000, 4002, 4004, 4007, 4009, 4011, 4014, 4016, 4018, 4020, 4023, 4025, 4027, 4030, 4032, 4034, 4037, 4039, 4041, 4044, 4046, 4048, 4051, 4053, 4055, 4058, 4060, 4063, 4065, 4067, 4070, 4072, 4074, 4077, 4079, 4081, 4084, 4086, 4088, 4091, 4093, 4096, 4098, 4100, 4103, 4105, 4107, 4110, 4112, 4114, 4117, 4119, 4122, 4124, 4126, 4129, 4131, 4134, 4136, 4138, 4141, 4143, 4145, 4148, 4150, 4153, 4155, 4157, 4160, 4162, 4165, 4167, 4170, 4172, 4174, 4177, 4179, 4182, 4184, 4186, 4189, 4191, 4194, 4196, 4199, 4201, 4203, 4206, 4208, 4211, 4213, 4216, 4218, 4220, 4223, 4225, 4228, 4230, 4233, 4235, 4237, 4240, 4242, 4245, 4247, 4250, 4252, 4255, 4257, 4260, 4262, 4265, 4267, 4269, 4272, 4274, 4277, 4279, 4282, 4284, 4287, 4289, 4292, 4294, 4297, 4299, 4302, 4304, 4307, 4309, 4312, 4314, 4317, 4319, 4322, 4324, 4327, 4329, 4332, 4334, 4337, 4339 }; // Four octaves up and down. // value = ((index/12)^2)*4096 where index = -48..48 const PFRACT pfSemiTones[] = { // 0 1 2 3 4 5 6 7 256, 271, 287, 304, 322, 341, 362, 383, 406, 430, 456, 483, 512, 542, 574, 608, 645, 683, 724, 767, 812, 861, 912, 966, 1024, 1084, 1149, 1217, 1290, 1366, 1448, 1534, 1625, 1722, 1824, 1933, 2048, 2169, 2298, 2435, 2580, 2733, 2896, 3068, 3250, 3444, 3649, 3866, 4096, 4339, 4597, 4870, 5160, 5467, 5792, 6137, 6501, 6888, 7298, 7732, 8192, 8679, 9195, 9741, 10321, 10935, 11585, 12274, 13003, 13777, 14596, 15464, 16384, 17358, 18390, 19483, 20642, 21870, 23170, 24548, 26007, 27554, 29192, 30928, 32768, 34716, 36780, 38967, 41285, 43740, 46340, 49096, 52015, 55108, 58385, 61857, 65536 }; // dB conversion table. // value = (((index / 100)^10)^.5)*4095 where index = MINDB*10..MAXDB*10 const /*VFRACT*/SHORT vfDbToVolume[] = { // 0 1 2 3 4 5 6 7 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 21, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 28, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 34, 34, 34, 35, 35, 36, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 40, 41, 41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51, 52, 52, 53, 53, 54, 55, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 63, 64, 64, 65, 66, 67, 67, 68, 69, 70, 71, 71, 72, 73, 74, 75, 76, 77, 78, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 97, 98, 99, 100, 101, 102, 104, 105, 106, 107, 108, 110, 111, 112, 114, 115, 116, 118, 119, 120, 122, 123, 125, 126, 128, 129, 130, 132, 134, 135, 137, 138, 140, 141, 143, 145, 146, 148, 150, 152, 153, 155, 157, 159, 161, 163, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 185, 187, 189, 191, 193, 195, 198, 200, 202, 205, 207, 210, 212, 214, 217, 219, 222, 225, 227, 230, 232, 235, 238, 241, 243, 246, 249, 252, 255, 258, 261, 264, 267, 270, 273, 276, 280, 283, 286, 289, 293, 296, 300, 303, 307, 310, 314, 317, 321, 325, 329, 332, 336, 340, 344, 348, 352, 356, 360, 364, 369, 373, 377, 382, 386, 391, 395, 400, 404, 409, 414, 419, 423, 428, 433, 438, 443, 449, 454, 459, 464, 470, 475, 481, 486, 492, 498, 503, 509, 515, 521, 527, 533, 539, 546, 552, 558, 565, 571, 578, 585, 591, 598, 605, 612, 619, 626, 634, 641, 649, 656, 664, 671, 679, 687, 695, 703, 711, 719, 728, 736, 745, 753, 762, 771, 780, 789, 798, 807, 817, 826, 836, 845, 855, 865, 875, 885, 895, 906, 916, 927, 938, 948, 959, 971, 982, 993, 1005, 1016, 1028, 1040, 1052, 1064, 1077, 1089, 1102, 1114, 1127, 1140, 1154, 1167, 1181, 1194, 1208, 1222, 1236, 1250, 1265, 1280, 1294, 1309, 1325, 1340, 1355, 1371, 1387, 1403, 1419, 1436, 1452, 1469, 1486, 1504, 1521, 1539, 1556, 1574, 1593, 1611, 1630, 1649, 1668, 1687, 1707, 1726, 1746, 1767, 1787, 1808, 1829, 1850, 1871, 1893, 1915, 1937, 1959, 1982, 2005, 2028, 2052, 2076, 2100, 2124, 2149, 2173, 2199, 2224, 2250, 2276, 2302, 2329, 2356, 2383, 2411, 2439, 2467, 2496, 2524, 2554, 2583, 2613, 2643, 2674, 2705, 2736, 2768, 2800, 2833, 2865, 2899, 2932, 2966, 3000, 3035, 3070, 3106, 3142, 3178, 3215, 3252, 3290, 3328, 3367, 3406, 3445, 3485, 3525, 3566, 3607, 3649, 3691, 3734, 3777, 3821, 3865, 3910, 3955, 4001, 4048, 4095 }; /***************************************************************************** * CDigitalAudio::VRELToVFRACT() ***************************************************************************** * Translate between VREL and VFRACT, clamping if necessary. */ VFRACT CDigitalAudio::VRELToVFRACT(VREL vrVolume) { vrVolume /= 10; if (vrVolume < MINDB * 10) vrVolume = MINDB * 10; else if (vrVolume >= MAXDB * 10) vrVolume = MAXDB * 10; return (::vfDbToVolume[vrVolume - MINDB * 10]); } /***************************************************************************** * CDigitalAudio::PRELToPFRACT() ***************************************************************************** * Translates from PREL to PFRACT, clamping if necessary. */ PFRACT CDigitalAudio::PRELToPFRACT(PREL prPitch) { PFRACT pfPitch = 0; PREL prOctave; if (prPitch > 100) { if (prPitch > 4800) { prPitch = 4800; } prOctave = prPitch / 100; prPitch = prPitch % 100; pfPitch = ::pfCents[prPitch + 100]; pfPitch <<= prOctave / 12; prOctave = prOctave % 12; pfPitch *= ::pfSemiTones[prOctave + 48]; pfPitch >>= 12; } else if (prPitch < -100) { if (prPitch < -4800) { prPitch = -4800; } prOctave = prPitch / 100; prPitch = (-prPitch) % 100; pfPitch = ::pfCents[100 - prPitch]; pfPitch >>= ((-prOctave) / 12); prOctave = (-prOctave) % 12; pfPitch *= ::pfSemiTones[48 - prOctave]; pfPitch >>= 12; } else { pfPitch = ::pfCents[prPitch + 100]; } return (pfPitch); } /***************************************************************************** * CDigitalAudio::ClearVoice() ***************************************************************************** * Clear this voice in the Digital Audio Engine. The wave object can go away now. */ void CDigitalAudio::ClearVoice() { if (m_Source.m_pWave != NULL) { m_Source.m_pWave->PlayOff(); m_Source.m_pWave->Release(); // Releases wave structure. m_Source.m_pWave = NULL; } } /***************************************************************************** * CDigitalAudio::StartVoice() ***************************************************************************** * Start a voice on the given synth and sample. */ STIME CDigitalAudio::StartVoice(CSynth *pSynth, CSourceSample *pSample, VREL vrBaseLVolume, VREL vrBaseRVolume, PREL prBasePitch, long lKey) { m_vrBaseLVolume = vrBaseLVolume; m_vrBaseRVolume = vrBaseRVolume; m_vfLastLVolume = VRELToVFRACT(MIN_VOLUME); m_vfLastRVolume = VRELToVFRACT(MIN_VOLUME); m_vrLastLVolume = 0; m_vrLastRVolume = 0; m_prLastPitch = 0; m_Source = *pSample; m_pnWave = pSample->m_pWave->m_pnWave; m_pSynth = pSynth; pSample->m_pWave->AddRef(); // Keeps track of Wave usage. pSample->m_pWave->PlayOn(); prBasePitch += pSample->m_prFineTune; prBasePitch += ((lKey - pSample->m_bMIDIRootKey) * 100); m_pfBasePitch = PRELToPFRACT(prBasePitch); m_pfBasePitch *= pSample->m_dwSampleRate; m_pfBasePitch /= pSynth->m_dwSampleRate; m_pfLastPitch = m_pfBasePitch; m_fElGrande = pSample->m_dwSampleLength >= 0x80000; // Greater than 512k. if ((pSample->m_dwLoopEnd - pSample->m_dwLoopStart) >= 0x80000) { // We can't handle loops greater than 1 meg! m_Source.m_bOneShot = TRUE; } m_ullLastSample = 0; m_ullLoopStart = pSample->m_dwLoopStart; m_ullLoopStart = m_ullLoopStart << 12; m_ullLoopEnd = pSample->m_dwLoopEnd; m_ullLoopEnd = m_ullLoopEnd << 12; m_ullSampleLength = pSample->m_dwSampleLength; m_ullSampleLength = m_ullSampleLength << 12; m_pfLastSample = 0; m_pfLoopStart = (long) m_ullLoopStart; m_pfLoopEnd = (long) m_ullLoopEnd; if (m_pfLoopEnd <= m_pfLoopStart) // Should never happen, but death if it does! { m_Source.m_bOneShot = TRUE; } if (m_fElGrande) { m_pfSampleLength = 0x7FFFFFFF; } else { m_pfSampleLength = (long) m_ullSampleLength; } return (0); // !!! what is this return value? } /* If the wave is bigger than one meg, the index can overflow. Solve this by assuming no mix session will ever be as great as one meg AND loops are never that long. We keep all our fractional indexes in two variables. In one case, m_pfLastSample, is the normal mode where the lower 12 bits are the fraction and the upper 20 bits are the index. And, m_ullLastSample is a LONGLONG with an extra 32 bits of index. The mix engine does not want the LONGLONGs, so we need to track the variables in the LONGLONGs and prepare them for the mixer as follows: Prior to mixing, if the sample is large (m_fElGrande is set), BeforeSampleMix() is called. This finds the starting point for the mix, which is either the current position or the start of the loop, whichever is earlier. It subtracts this starting point from the LONGLONG variables and stores an offset in m_dwAddressUpper. It also adjusts the pointer to the wave data appropriately. AfterSampleMix() does the inverse, reconstructing the the LONGLONG indeces and returning everthing back to normal. */ /***************************************************************************** * CDigitalAudio::BeforeBigSampleMix() ***************************************************************************** * Setup before doing a large sample mix/loop. */ void CDigitalAudio::BeforeBigSampleMix() { if (m_fElGrande) { ULONGLONG ullBase = 0; DWORD dwBase; if (m_Source.m_bOneShot) { ullBase = m_ullLastSample; } else { if (m_ullLastSample < m_ullLoopStart) { ullBase = m_ullLastSample; } else { ullBase = m_ullLoopStart; } } ullBase >>= 12; dwBase = (DWORD) ullBase & 0xFFFFFFFE; // Clear bottom bit so 8 bit pointer aligns with short. ullBase = dwBase; ullBase <<= 12; m_dwAddressUpper = dwBase; m_pfLastSample = (long) (m_ullLastSample - ullBase); if ((m_ullLoopEnd - ullBase) < 0x7FFFFFFF) { m_pfLoopStart = (long) (m_ullLoopStart - ullBase); m_pfLoopEnd = (long) (m_ullLoopEnd - ullBase); } else { m_pfLoopStart = 0; m_pfLoopEnd = 0x7FFFFFFF; } ullBase = m_ullSampleLength - ullBase; if (ullBase > 0x7FFFFFFF) { m_pfSampleLength = 0x7FFFFFFF; } else { m_pfSampleLength = (long) ullBase; } if (m_Source.m_bSampleType & SFORMAT_8) { dwBase >>= 1; } m_pnWave = &m_Source.m_pWave->m_pnWave[dwBase]; } } /***************************************************************************** * CDigitalAudio::AfterBigSampleMix() ***************************************************************************** * Cleanup after doing a large sample mix/loop. */ void CDigitalAudio::AfterBigSampleMix() { m_pnWave = m_Source.m_pWave->m_pnWave; if (m_fElGrande) { ULONGLONG ullBase = m_dwAddressUpper; m_ullLastSample = m_pfLastSample; m_ullLastSample += (ullBase << 12); m_dwAddressUpper = 0; } } /***************************************************************************** * CDigitalAudio::Mix() ***************************************************************************** * Do a mix on this buffer. This handles loops and calling the different mix * functions (depending on format). */ BOOL CDigitalAudio::Mix(short *pBuffer, DWORD dwLength, // length in SAMPLES VREL vrVolumeL,VREL vrVolumeR, PREL prPitch, DWORD dwStereo) { PFRACT pfDeltaPitch; PFRACT pfEnd; PFRACT pfLoopLen; PFRACT pfNewPitch; VFRACT vfNewLVolume; VFRACT vfNewRVolume; VFRACT vfDeltaLVolume; VFRACT vfDeltaRVolume; DWORD dwPeriod = 64; DWORD dwSoFar; DWORD dwStart; // position in WORDs DWORD dwMixChoice = dwStereo ? SPLAY_STEREO : 0; if (dwLength == 0) // Attack was instant. { m_pfLastPitch = (m_pfBasePitch * PRELToPFRACT(prPitch)) >> 12; m_vfLastLVolume = VRELToVFRACT(m_vrBaseLVolume + vrVolumeL); m_vfLastRVolume = VRELToVFRACT(m_vrBaseRVolume + vrVolumeR); m_prLastPitch = prPitch; m_vrLastLVolume = vrVolumeL; m_vrLastRVolume = vrVolumeR; return (TRUE); } if ((m_Source.m_pWave == NULL) || (m_Source.m_pWave->m_pnWave == NULL)) { return FALSE; } DWORD dwMax = abs(vrVolumeL - m_vrLastLVolume); m_vrLastLVolume = vrVolumeL; dwMax = max((long)dwMax,abs(vrVolumeR - m_vrLastRVolume)); m_vrLastRVolume = vrVolumeR; dwMax = max((long)dwMax,abs(prPitch - m_prLastPitch) << 1); dwMax >>= 1; m_prLastPitch = prPitch; if (dwMax > 0) { dwPeriod = (dwLength << 3) / dwMax; if (dwPeriod > 512) { dwPeriod = 512; } else if (dwPeriod < 1) { dwPeriod = 1; } } else { dwPeriod = 512; // Make it happen anyway. } // This makes MMX sound a little better (MMX bug will be fixed) dwPeriod += 3; dwPeriod &= 0xFFFFFFFC; pfNewPitch = m_pfBasePitch * PRELToPFRACT(prPitch); pfNewPitch >>= 12; pfDeltaPitch = MulDiv(pfNewPitch - m_pfLastPitch,dwPeriod << 8,dwLength); vfNewLVolume = VRELToVFRACT(m_vrBaseLVolume + vrVolumeL); vfNewRVolume = VRELToVFRACT(m_vrBaseRVolume + vrVolumeR); vfDeltaLVolume = MulDiv(vfNewLVolume - m_vfLastLVolume,dwPeriod << 8,dwLength); vfDeltaRVolume = MulDiv(vfNewRVolume - m_vfLastRVolume,dwPeriod << 8,dwLength); if (m_fMMXEnabled && (dwLength > 8)) { dwMixChoice |= SPLAY_MMX; } dwMixChoice |= m_Source.m_bSampleType; dwStart = 0; for (;;) { if (dwLength <= 8) { dwMixChoice &= ~SPLAY_MMX; } if (m_fElGrande) { BeforeBigSampleMix(); } if (m_Source.m_bOneShot) { pfEnd = m_pfSampleLength; pfLoopLen = 0; } else { pfEnd = m_pfLoopEnd; pfLoopLen = m_pfLoopEnd - m_pfLoopStart; if (pfLoopLen <= pfNewPitch) { return FALSE; } } switch (dwMixChoice) { case SFORMAT_8 | SPLAY_STEREO : dwSoFar = Mix8(&pBuffer[dwStart],dwLength,dwPeriod, vfDeltaLVolume, vfDeltaRVolume, pfDeltaPitch, pfEnd, pfLoopLen); break; case SFORMAT_8 : dwSoFar = MixMono8(&pBuffer[dwStart],dwLength,dwPeriod, vfDeltaLVolume, pfDeltaPitch, pfEnd, pfLoopLen); break; case SFORMAT_16 | SPLAY_STEREO : dwSoFar = Mix16(&pBuffer[dwStart],dwLength,dwPeriod, vfDeltaLVolume, vfDeltaRVolume, pfDeltaPitch, pfEnd, pfLoopLen); break; case SFORMAT_16 : dwSoFar = MixMono16(&pBuffer[dwStart],dwLength,dwPeriod, vfDeltaLVolume, pfDeltaPitch, pfEnd, pfLoopLen); break; #ifdef MMX_ENABLED case SFORMAT_8 | SPLAY_MMX | SPLAY_STEREO : dwSoFar = Mix8X(&pBuffer[dwStart],dwLength,dwPeriod, vfDeltaLVolume, vfDeltaRVolume , pfDeltaPitch, pfEnd, pfLoopLen); break; case SFORMAT_16 | SPLAY_MMX | SPLAY_STEREO : dwSoFar = Mix16X(&pBuffer[dwStart],dwLength,dwPeriod, vfDeltaLVolume, vfDeltaRVolume, pfDeltaPitch, pfEnd, pfLoopLen); break; case SFORMAT_8 | SPLAY_MMX : dwSoFar = MixMono8X(&pBuffer[dwStart],dwLength,dwPeriod, vfDeltaLVolume, pfDeltaPitch, pfEnd, pfLoopLen); break; case SFORMAT_16 | SPLAY_MMX : dwSoFar = MixMono16X(&pBuffer[dwStart],dwLength,dwPeriod, vfDeltaLVolume, pfDeltaPitch, pfEnd, pfLoopLen); break; #endif default : return (FALSE); } if (m_fElGrande) { AfterBigSampleMix(); } if (m_Source.m_bOneShot) { if (dwSoFar < dwLength) { return (FALSE); } break; } else { if (dwSoFar >= dwLength) break; // !!! even though we often handle loops in the mix function, sometimes // we don't, so we still need this code. // otherwise we must have reached the loop's end. dwStart += dwSoFar << dwStereo; dwLength -= dwSoFar; m_pfLastSample -= (m_pfLoopEnd - m_pfLoopStart); } } m_vfLastLVolume = vfNewLVolume; m_vfLastRVolume = vfNewRVolume; m_pfLastPitch = pfNewPitch; return (TRUE); } /***************************************************************************** * CVoice::CVoice() ***************************************************************************** * Constructor for the CVoice object. */ CVoice::CVoice() { m_pControl = NULL; m_pPitchBendIn = NULL; m_pExpressionIn = NULL; m_dwPriority = 0; m_nPart = 0; m_nKey = 0; m_fInUse = FALSE; m_fSustainOn = FALSE; m_fNoteOn = FALSE; m_fTag = FALSE; m_stStartTime = 0; m_stStopTime = 0x7fffffffffffffff; m_vrVolume = 0; m_fAllowOverlap = FALSE; } /***************************************************************************** * svrPanToVREL[] ***************************************************************************** * A table for 0-127 translation to log scale. * value = log10(index/127) * 1000 where index = 0..128 */ const VREL svrPanToVREL[] = { // 0 1 2 3 4 5 6 7 -2500, -2103, -1802, -1626, -1501, -1404, -1325, -1258, -1200, -1149, -1103, -1062, -1024, -989, -957, -927, -899, -873, -848, -825, -802, -781, -761, -742, -723, -705, -688, -672, -656, -641, -626, -612, -598, -585, -572, -559, -547, -535, -524, -512, -501, -491, -480, -470, -460, -450, -441, -431, -422, -413, -404, -396, -387, -379, -371, -363, -355, -347, -340, -332, -325, -318, -311, -304, -297, -290, -284, -277, -271, -264, -258, -252, -246, -240, -234, -228, -222, -217, -211, -206, -200, -195, -189, -184, -179, -174, -169, -164, -159, -154, -149, -144, -140, -135, -130, -126, -121, -117, -112, -108, -103, -99, -95, -90, -86, -82, -78, -74, -70, -66, -62, -58, -54, -50, -46, -43, -39, -35, -31, -28, -24, -21, -17, -13, -10, -6, -3, 0 }; /***************************************************************************** * CVoice::StopVoice() ***************************************************************************** * Stop the voice if it is playing. Reset the envelope generators, sustain. */ void CVoice::StopVoice(STIME stTime) { if (m_fNoteOn) { if (stTime <= m_stStartTime) stTime = m_stStartTime + 1; m_PitchEG.StopVoice(stTime); m_VolumeEG.StopVoice(stTime); m_fNoteOn = FALSE; m_fSustainOn = FALSE; m_stStopTime = stTime; } } /***************************************************************************** * CVoice::QuickStopVoice() ***************************************************************************** * Stop the voice ASAP. If the note is on or sustaining, turn everything off * now, otherwise we just have to stop the volume EG (kill the decay curve). */ void CVoice::QuickStopVoice(STIME stTime) { m_fTag = TRUE; if (m_fNoteOn || m_fSustainOn) { if (stTime <= m_stStartTime) stTime = m_stStartTime + 1; m_PitchEG.StopVoice(stTime); m_VolumeEG.QuickStopVoice(stTime,m_pSynth->m_dwSampleRate); m_fNoteOn = FALSE; m_fSustainOn = FALSE; m_stStopTime = stTime; } else { m_VolumeEG.QuickStopVoice(m_stStopTime,m_pSynth->m_dwSampleRate); } } /***************************************************************************** * CVoice::StartVoice() ***************************************************************************** * Start the voice with all the given parameters for pitch, mod, pan, etc. * The region must have an articulation and a wave. Mix it now if the * start time dictates. */ BOOL CVoice::StartVoice(CSynth *pSynth, CSourceRegion *pRegion, STIME stStartTime, CModWheelIn * pModWheelIn, CPitchBendIn * pPitchBendIn, CExpressionIn * pExpressionIn, CVolumeIn * pVolumeIn, CPanIn * pPanIn, WORD nKey,WORD nVelocity, VREL vrVolume, PREL prPitch) { CSourceArticulation * pArticulation = pRegion->m_pArticulation; if (pArticulation == NULL) { return FALSE; } // if we're going to handle volume later, don't read it now. if (!pSynth->m_fAllowVolumeChangeWhilePlayingNote) vrVolume += pVolumeIn->GetVolume(stStartTime); prPitch += pRegion->m_prTuning; m_dwGroup = pRegion->m_bGroup; m_fAllowOverlap = pRegion->m_bAllowOverlap; m_pSynth = pSynth; vrVolume += CMIDIRecorder::VelocityToVolume(nVelocity); // * (long) pArticulation->m_sVelToVolScale) / -9600); vrVolume += pRegion->m_vrAttenuation; m_lDefaultPan = pRegion->m_pArticulation->m_sDefaultPan; // ignore pan here if allowing pan to vary after note starts VREL vrLVolume; VREL vrRVolume; if (pSynth->m_dwStereo && !pSynth->m_fAllowPanWhilePlayingNote) { long lPan = pPanIn->GetPan(stStartTime) + m_lDefaultPan; if (lPan < 0) lPan = 0; if (lPan > 127) lPan = 127; vrLVolume = ::svrPanToVREL[127 - lPan] + vrVolume; vrRVolume = ::svrPanToVREL[lPan] + vrVolume; } else { vrLVolume = vrVolume; vrRVolume = vrVolume; } m_stMixTime = m_LFO.StartVoice(&pArticulation->m_LFO, stStartTime, pModWheelIn); STIME stMixTime = m_PitchEG.StartVoice(&pArticulation->m_PitchEG, stStartTime, nKey, nVelocity); if (stMixTime < m_stMixTime) { m_stMixTime = stMixTime; } stMixTime = m_VolumeEG.StartVoice(&pArticulation->m_VolumeEG, stStartTime, nKey, nVelocity); if (stMixTime < m_stMixTime) { m_stMixTime = stMixTime; } if (m_stMixTime > pSynth->m_stMaxSpan) { m_stMixTime = pSynth->m_stMaxSpan; } // Make sure we have a pointer to the wave ready: if ((pRegion->m_Sample.m_pWave == NULL) || (pRegion->m_Sample.m_pWave->m_pnWave == NULL)) { return (FALSE); // Do nothing if no sample. } m_DigitalAudio.StartVoice(pSynth,&pRegion->m_Sample, vrLVolume, vrRVolume, prPitch, (long)nKey); m_pPitchBendIn = pPitchBendIn; m_pExpressionIn = pExpressionIn; m_pPanIn = pPanIn; m_pVolumeIn = pVolumeIn; m_fNoteOn = TRUE; m_fTag = FALSE; m_stStartTime = stStartTime; m_stLastMix = stStartTime - 1; m_stStopTime = 0x7fffffffffffffff; if (m_stMixTime == 0) { // zero length attack, be sure it isn't missed.... PREL prNewPitch = GetNewPitch(stStartTime); VREL vrVolumeL, vrVolumeR; GetNewVolume(stStartTime, vrVolumeL, vrVolumeR); if (m_stMixTime > pSynth->m_stMaxSpan) { m_stMixTime = pSynth->m_stMaxSpan; } m_DigitalAudio.Mix(NULL, 0, vrVolumeL, vrVolumeR, prNewPitch, m_pSynth->m_dwStereo); } m_vrVolume = 0; return (TRUE); } /***************************************************************************** * CVoice::ClearVoice() ***************************************************************************** * Clear the voice (just forward to the digital audio peer object). */ void CVoice::ClearVoice() { m_fInUse = FALSE; m_DigitalAudio.ClearVoice(); } /***************************************************************************** * CVoice::GetNewVolume() ***************************************************************************** * Return the volume delta at time . * Volume is sum of volume envelope, LFO, expression, optionally the * channel volume if we're allowing it to change, and optionally the current * pan if we're allowing that to change. * This will be added to the base volume calculated in CVoice::StartVoice(). */ void CVoice::GetNewVolume(STIME stTime, VREL& vrVolume, VREL &vrVolumeR) { STIME stMixTime; vrVolume = m_VolumeEG.GetVolume(stTime,&stMixTime); if (stMixTime < m_stMixTime) m_stMixTime = stMixTime; // save pre-LFO volume for code that detects whether this note is off m_vrVolume = vrVolume; vrVolume += m_LFO.GetVolume(stTime,&stMixTime); if (stMixTime < m_stMixTime) m_stMixTime = stMixTime; vrVolume += m_pExpressionIn->GetVolume(stTime); if (m_pSynth->m_fAllowVolumeChangeWhilePlayingNote) vrVolume += m_pVolumeIn->GetVolume(stTime); vrVolume += m_pSynth->m_vrGainAdjust; vrVolumeR = vrVolume; // handle pan here if allowing pan to vary after note starts if (m_pSynth->m_dwStereo && m_pSynth->m_fAllowPanWhilePlayingNote) { // add current pan & instrument default pan LONG lPan = m_pPanIn->GetPan(stTime) + m_lDefaultPan; // don't go off either end.... if (lPan < 0) lPan = 0; if (lPan > 127) lPan = 127; vrVolume += ::svrPanToVREL[127 - lPan]; vrVolumeR += ::svrPanToVREL[lPan]; } } /***************************************************************************** * CVoice::GetNewPitch() ***************************************************************************** * Returns the current pitch for time . * Pitch is the sum of the pitch LFO, the pitch envelope, and the current * pitch bend. */ PREL CVoice::GetNewPitch(STIME stTime) { STIME stMixTime; PREL prPitch = m_LFO.GetPitch(stTime,&stMixTime); if (m_stMixTime > stMixTime) m_stMixTime = stMixTime; prPitch += m_PitchEG.GetPitch(stTime,&stMixTime); if (m_stMixTime > stMixTime) m_stMixTime = stMixTime; prPitch += m_pPitchBendIn->GetPitch(stTime); return prPitch; } /***************************************************************************** * CVoice::Mix() ***************************************************************************** * Mix this voice into the given buffer. Determine certain volume and pitch * parameters, then call into the Digital Audio Engine. */ DWORD CVoice::Mix( short *pBuffer, DWORD dwLength, STIME stStart, STIME stEnd) { BOOL fInUse = TRUE; BOOL fFullMix = TRUE; STIME stEndMix = stStart; STIME stStartMix = m_stStartTime; if (stStartMix < stStart) { stStartMix = stStart; } if (m_stLastMix >= stEnd) { return (0); } if (m_stLastMix >= stStartMix) { stStartMix = m_stLastMix; } while (stStartMix < stEnd && fInUse) { stEndMix = stStartMix + m_stMixTime; if (stEndMix > stEnd) { stEndMix = stEnd; } m_stMixTime = m_pSynth->m_stMaxSpan; if ((m_stLastMix < m_stStopTime) && (m_stStopTime < stEnd)) { if (m_stMixTime > (m_stStopTime - m_stLastMix)) { m_stMixTime = m_stStopTime - m_stLastMix; } } PREL prPitch = GetNewPitch(stEndMix); VREL vrVolume, vrVolumeR; GetNewVolume(stEndMix, vrVolume, vrVolumeR); if (m_VolumeEG.InRelease(stEndMix)) { if (m_vrVolume < PERCEIVED_MIN_VOLUME) // End of release slope { fInUse = FALSE; } } fFullMix = m_DigitalAudio.Mix(&pBuffer[(stStartMix - stStart) << m_pSynth->m_dwStereo], (DWORD) (stEndMix - stStartMix), vrVolume, vrVolumeR, prPitch, m_pSynth->m_dwStereo); stStartMix = stEndMix; } m_fInUse = fInUse && fFullMix; if (!m_fInUse) { ClearVoice(); m_stStopTime = stEndMix; // For measurement purposes. } m_stLastMix = stEndMix; return (dwLength); }