GO_SAMPLER* GOSoundEngine::StartSample(const GOSoundProvider* pipe, int sampler_group_id, unsigned audio_group, unsigned velocity, unsigned delay, uint64_t last_stop) { unsigned delay_samples = (delay * m_SampleRate) / (1000); uint64_t start_time = m_CurrentTime + delay_samples; uint64_t released_time = ((start_time - last_stop) * 1000) / m_SampleRate; if (released_time > (unsigned)-1) released_time = (unsigned)-1; const GOAudioSection* attack = pipe->GetAttack(velocity, released_time); if (!attack || attack->GetChannels() == 0) return NULL; GO_SAMPLER* sampler = m_SamplerPool.GetSampler(); if (sampler) { sampler->pipe = pipe; sampler->velocity = velocity; attack->InitStream (&m_ResamplerCoefs ,&sampler->stream ,GetRandomFactor() * pipe->GetTuning() / (float)m_SampleRate ); const float playback_gain = pipe->GetGain() * attack->GetNormGain(); sampler->fader.NewConstant(playback_gain); sampler->delay = delay_samples; sampler->time = start_time; sampler->fader.SetVelocityVolume(sampler->pipe->GetVelocityVolume(sampler->velocity)); StartSampler(sampler, sampler_group_id, audio_group); } return sampler; }
/* * NotifyHandler - call back routine for notifications */ BOOL __export FAR PASCAL NotifyHandler( WORD id, DWORD data ) { notify *ptr; if( id == NFY_TASKIN || id == NFY_TASKOUT ) { if( GetCurrentTask() == ourTask ) { if( id == NFY_TASKIN ) { UnPauseSampler(); } else { PauseSampler(); } } return( FALSE ); } PauseSampler(); ptr = getNotifyData( data ); switch( id ) { case NFY_STARTDLL: _fmemcpy( &ptr->startdll, (LPVOID) data, sizeof( NFYSTARTDLL ) ); MyModuleFindHandle( &ptr->me, ptr->startdll.hModule ); HandleLibLoad( SAMP_CODE_LOAD, ptr->startdll.hModule ); break; case NFY_STARTTASK: if( ourTask == NULL ) { // handle spawned tasks ourTask = GetCurrentTask(); MyTaskFindHandle( &ptr->te, ptr->task ); MyModuleFindHandle( &ptr->me, ptr->te.hModule ); HandleLibLoad( SAMP_MAIN_LOAD, ptr->te.hModule ); StartSampler(); } else { MyTaskFindHandle( &ptr->te, ptr->task ); MyModuleFindHandle( &ptr->me, ptr->te.hModule ); HandleLibLoad( SAMP_CODE_LOAD, ptr->te.hModule ); } break; case NFY_EXITTASK: if( GetCurrentTask() == ourTask ) { // handle spawned tasks QuitSampler( &TotalTime ); SharedMemory->TaskEnded = TRUE; } break; } UnPauseSampler(); return( FALSE ); } /* NotifyHandler */
void GOSoundEngine::SwitchAttackSampler(GO_SAMPLER* handle) { if (!handle->pipe) return; unsigned time = 1000; const GOSoundProvider* this_pipe = handle->pipe; const GOAudioSection* section = this_pipe->GetAttack(handle->velocity, time); if (!section) return; if (handle->is_release) return; GO_SAMPLER* new_sampler = m_SamplerPool.GetSampler(); if (new_sampler != NULL) { *new_sampler = *handle; handle->pipe = this_pipe; handle->time = m_CurrentTime + 1; float gain_target = this_pipe->GetGain() * section->GetNormGain(); unsigned cross_fade_len = this_pipe->GetReleaseCrossfadeLength(); handle->fader.NewAttacking(gain_target, cross_fade_len, m_SampleRate); section->InitAlignedStream(&handle->stream, &new_sampler->stream); handle->is_release = false; new_sampler->is_release = true; new_sampler->time = m_CurrentTime; new_sampler->fader.StartDecay(cross_fade_len, m_SampleRate); new_sampler->fader.SetVelocityVolume(new_sampler->pipe->GetVelocityVolume(new_sampler->velocity)); StartSampler(new_sampler, new_sampler->sampler_group_id, new_sampler->audio_group_id); } }
void GOSoundEngine::CreateReleaseSampler(GO_SAMPLER* handle) { if (!handle->pipe) return; /* The beloow code creates a new sampler to playback the release, the * following code takes the active sampler for this pipe (which will be * in either the attack or loop section) and sets the fadeout property * which will decay this portion of the pipe. The sampler will * automatically be placed back in the pool when the fade restores to * zero. */ unsigned cross_fade_len = handle->pipe->GetReleaseCrossfadeLength(); handle->fader.StartDecay(cross_fade_len, m_SampleRate); handle->is_release = true; const GOSoundProvider* this_pipe = handle->pipe; float vol = (handle->sampler_group_id < 0) ? 1.0f : m_Windchests[handle->sampler_group_id]->GetWindchestVolume(); // FIXME: this is wrong... the intention is to not create a release for a // sample being played back with zero amplitude but this is a comparison // against a double. We should test against a minimum level. if (vol) { const GOAudioSection* release_section = this_pipe->GetRelease(&handle->stream, ((double)(m_CurrentTime - handle->time)) / m_SampleRate); if (!release_section) return; GO_SAMPLER* new_sampler = m_SamplerPool.GetSampler(); if (new_sampler != NULL) { new_sampler->pipe = this_pipe; new_sampler->time = m_CurrentTime + 1; new_sampler->velocity = handle->velocity; unsigned gain_decay_length = 0; float gain_target = this_pipe->GetGain() * release_section->GetNormGain(); const bool not_a_tremulant = (handle->sampler_group_id >= 0); if (not_a_tremulant) { /* Because this sampler is about to be moved to a detached * windchest, we must apply the gain of the existing windchest * to the gain target for this fader - otherwise the playback * volume on the detached chest will not match the volume on * the existing chest. */ gain_target *= vol; if (m_ScaledReleases) { /* Note: "time" is in milliseconds. */ int time = ((m_CurrentTime - handle->time) * 1000) / m_SampleRate; /* TODO: below code should be replaced by a more accurate model of the attack to get a better estimate of the amplitude when playing very short notes * estimating attack duration from pipe midi pitch */ unsigned midikey_frequency = this_pipe->GetMidiKeyNumber(); /* if MidiKeyNumber is not within an organ 64 feet to 1 foot pipes, we assume average pipe (MIDI = 60)*/ if (midikey_frequency >133 || midikey_frequency == 0 ) midikey_frequency = 60; /* attack duration is assumed 50 ms above MIDI 96, 800 ms below MIDI 24 and linear in between */ float attack_duration = 50.0f; if (midikey_frequency < 96 ) { if (midikey_frequency < 24) attack_duration = 500.0f; else attack_duration = 500.0f + ( ( 24.0f - (float)midikey_frequency ) * 6.25f ); } /* calculate gain (gain_target) to apply to tail amplitude in function of when the note is released during the attack */ if (time < (int)attack_duration) { float attack_index = (float)time / attack_duration; float gain_delta = ( 0.2f + ( 0.8f * ( 2.0f * attack_index - (attack_index * attack_index) ))); gain_target *= gain_delta; } /* calculate the volume decay to be applied to the release to take into account the fact reverb is not completely formed during staccato * time to full reverb is estimated in function of release length * for an organ with a release length of 5 seconds or more, time_to_full_reverb is around 350 ms * for an organ with a release length of 1 second or less, time_to_full_reverb is around 100 ms * time_to_full_reverb is linear in between */ int time_to_full_reverb = ((60 * release_section->GetLength()) / release_section->GetSampleRate()) + 40; if (time_to_full_reverb > 350 ) time_to_full_reverb = 350; if (time_to_full_reverb < 100 ) time_to_full_reverb = 100; if (time < time_to_full_reverb) { /* in function of note duration, fading happens between: * 200 ms and 6 s for release with little reverberation e.g. short release * 700 ms and 6 s for release with large reverberation e.g. long release */ gain_decay_length = time_to_full_reverb + 6000 * time / time_to_full_reverb; } } } unsigned cross_fade_len = this_pipe->GetReleaseCrossfadeLength(); new_sampler->fader.NewAttacking(gain_target, cross_fade_len, m_SampleRate); if (m_ReleaseLength > 0) { if ( m_ReleaseLength < gain_decay_length || gain_decay_length == 0 ) gain_decay_length = m_ReleaseLength; } if (gain_decay_length > 0) new_sampler->fader.StartDecay(gain_decay_length, m_SampleRate); if (m_ReleaseAlignmentEnabled && release_section->SupportsStreamAlignment()) { release_section->InitAlignedStream(&new_sampler->stream, &handle->stream); } else { release_section->InitStream(&m_ResamplerCoefs, &new_sampler->stream, this_pipe->GetTuning() / (float)m_SampleRate); } new_sampler->is_release = true; int windchest_index; if (not_a_tremulant) { /* detached releases are enabled and the pipe was on a regular * windchest. Play the release on the detached windchest */ windchest_index = 0; } else { /* detached releases are disabled (or this isn't really a pipe) * so put the release on the same windchest as the pipe (which * means it will still be affected by tremulants - yuck). */ windchest_index = handle->sampler_group_id; } new_sampler->fader.SetVelocityVolume(new_sampler->pipe->GetVelocityVolume(new_sampler->velocity)); StartSampler(new_sampler, windchest_index, handle->audio_group_id); handle->time = m_CurrentTime; } } }