/// <summary> /// Constructor created as a beginning for saving file sequence. Constructor opens new user dialog for saving file and saves new file path as an internal property. /// </summary> /// <param name="openFile">Current instance of openFile class; Thanks to this one can read all required properties</param> public SaveFile(OpenFile openFile) { _openFile = openFile; _samplesBuffer = new List <List <byte> >(); for (short i = 0; i < _openFile.getNumberOfChannels(); i++) { _samplesBuffer.Add(new List <byte>()); } //think about default format(s)... SaveFileDialog saveFile = new SaveFileDialog(); //default selected file type - mp3 saveFile.DefaultExt = ".mp3"; saveFile.Title = "Save modified audio file"; saveFile.Filter = "Audio files (*.wav, *.flac, *.mp3, *.m4a, *.ogg)|*.mp3; *.flac; *.wav; *.m4a; *.ogg|All files (*.*)|*.*"; //change UI, prepare app to be able to receive another file if (saveFile.ShowDialog() == true) { _filePath = saveFile.FileName; _fileExtension = _filePath.Substring(_filePath.LastIndexOf('.') + 1); } }
/// <summary> /// Method used for saving every single output sample (one-way data flow). /// </summary> /// <param name="value">Value of a sample (signed 16-bit short)</param> /// <param name="channel">Select channel number; Avoid selecting non-existing channel</param> public void saveSingleSample(short value, short channel) { var sampleBytes = BitConverter.GetBytes(value); if (channel < _openFile.getNumberOfChannels()) { _samplesBuffer[channel].Add(sampleBytes[0]); _samplesBuffer[channel].Add(sampleBytes[1]); } else { throw new InvalidOperationException("A non-existent channel has been called for save action."); } if (_samplesBuffer[_openFile.getNumberOfChannels() - 1].Count >= _openFile.getSampleRate() * 2) { bool buffersAreAligned = true; for (short i = 0; i < _openFile.getNumberOfChannels(); i++) { if (_samplesBuffer[i].Count != _openFile.getSampleRate() * 2) { buffersAreAligned = false; } } if (buffersAreAligned) { saveAudioChunk(); for (short i = (short)(_openFile.getNumberOfChannels() - 1); i >= 0; i--) { _samplesBuffer.RemoveAt(i); } for (short i = 0; i < _openFile.getNumberOfChannels(); i++) { _samplesBuffer.Add(new List <byte>()); } } } }
/// <summary> /// Constructor for initializing silence cutting algorithm /// </summary> /// <param name="mainWindow">Current instance of MainWindow class</param> /// <param name="audio_open">Current instance of OpenFile class</param> public CutSilence(MainWindow mainWindow, OpenFile audio_open) { audio_open_file = audio_open; _numberOfChannels = audio_open_file.getNumberOfChannels(); //ensure the lists have adequate number of channels - right dimension for (short i = 0; i < _numberOfChannels; i++) { _samples.Add(new List <short>()); _buffer.Add(new List <short>()); _temporarySamplesValues.Add(0); } double buffer_slider = mainWindow.A_r_time_slider.Value; //Value from slider Attack/Release time double attack_buffer = Math.Round(audio_open_file.getSampleRate() * (0.05 + buffer_slider * (5 / 7.0))); //Determines the number of samples to be checked when a sample below the threshold is detected. Prevents low value samples from being cut from a non-silent signal int release_buffer = (int)Math.Round(audio_open_file.getSampleRate() * (0.02 + buffer_slider * (2 / 7.0))); //Determines the number od samples to be saved before a sample above the treshold is detected during silence cutting. Protects against too aggressive starting of the sound double treshold_slider = mainWindow.Threshold_slider.Value; int threshold = 500 + (int)Math.Round(treshold_slider); //ZROBIĆ W ZALEŻNOŚCI OD OTRZYMANYCH WARTOŚCI double attack_smooth_silencing = attack_buffer - Math.Round(audio_open_file.getSampleRate() * 0.01); //Determines the length of the signal where slow muting of samples will be performed. In this case 10ms. double release_smooth_silencing = Math.Round(audio_open_file.getSampleRate() * 0.01); //Determines the length of the signal where slow unmuting of samples will be performed. In this case 10ms. for (int i = 0; i < (int)audio_open_file.getTotalSamplesNumber(); i++) { if (i == progress_counter * (((int)audio_open_file.getTotalSamplesNumber()) / 200)) //part of the program responsible for progress bar loading. { //A workaround for working status bar; Call it AS RARELY AS POSSIBLE as this kind of dispatcher hurts application's performance. //I suggest calling this part of code 50 times during whole convertion process; It will be enough to show the progress and it won't hurt the performance as well. mainWindow.Dispatcher.Invoke((Action)(() => { mainWindow.progress_bar.Value += 1; }), System.Windows.Threading.DispatcherPriority.ApplicationIdle); progress_counter++; } bool above_threshold = false; for (short j = 0; j < _numberOfChannels; j++) //Saving one sample for each channel in a given iteration of the loop { _temporarySamplesValues[j] = audio_open_file.getSampleValue(i, j); } for (short j = 0; j < _temporarySamplesValues.Count(); j++) { if (_temporarySamplesValues[j] >= threshold) //Checking if at least one sample for any channel exceeds the set threshold. If found, look no further { above_threshold = true; break; } } if (!above_threshold && sound) //Status update if a sample below the specified threshold is detected in the sound { sound = false; silence_verification = true; } if (sound) //First part - save samples if it's a sound { saving_samples(i); } if (silence_verification) //Second part - checking if silence is detected { if (buffer_counter > attack_smooth_silencing && buffer_counter < attack_buffer) //Part of the program responsible for smooth silencing of samples at the end of buffer. The silencing time is 10ms { buffer_counter++; smooth_silencing_counter++; for (short j = 0; j < _numberOfChannels; j++) { _samples[j].Add((short)(audio_open_file.getSampleValue(i, j) * ((attack_buffer - attack_smooth_silencing - smooth_silencing_counter) / (attack_buffer - attack_smooth_silencing)))); } } else { saving_samples(i); buffer_counter++; } if (above_threshold) //If any sample with a value exceeding the threshold is found, go back to the first part { sound = true; silence_verification = false; smooth_silencing_counter = 0; buffer_counter = 0; } if (buffer_counter == attack_buffer && silence_verification) //If all samples in the buffer were below the threshold go to third part. Silence verification is { //needed to rule out situations in which the last attempt in the buffer exceeds the threshold. Eliminate a situation where both ifs are true silence_cutting = true; silence_verification = false; buffer_counter = 0; smooth_silencing_counter = 0; } } if (silence_cutting) //Third part - cutting silence from the signal. { for (short j = 0; j < _numberOfChannels; j++) { _buffer[j].Add(audio_open_file.getSampleValue(i, j)); //Adding sample values to the buffer that is responsible for release time } if (release_counter < release_buffer) //Defining size of buffer { release_counter++; } else { for (short j = 0; j < _numberOfChannels; j++) { _buffer[j].RemoveAt(0); //Removing the oldest samples } } if (above_threshold) //The sample value has exceeded the threshold //uwazac bo czasami nie zdazy sie zapelnic cały bufor { if (release_counter == release_buffer) //checking if the buffer has been completely full { for (int k = 0; k < release_smooth_silencing; k++) { for (short j = 0; j < _numberOfChannels; j++) { _buffer[j][k] = (short)(_buffer[j][k] * (k / release_smooth_silencing)); //Adding samples with progressively larger values to achieve smooth transition } } } for (short j = 0; j < _numberOfChannels; j++) { _samples[j].AddRange(_buffer[j]); //Adding samples that are included in the release time } release_counter = 0; //Preparing the counter and buffers for re-operation for (short j = 0; j < _numberOfChannels; j++) { _buffer[j].Clear(); } silence_cutting = false; sound = true; } } } //int[] array = samples_first_channel.ToArray(); //samples_first_channel.Clear(); //TRZEBA BEDZIE CZYŚCIĆ BUFORY (buffer_first_channel.Clear(); buffer_second_channel.Clear();) NA WSZELKI WYPADEK JAKBY DZIAŁANIE PROGRAMU ZAKOŃCZYŁA SIE W POŁOWIE ORAZ BY PRZYGOTOWAĆ PROGRAM DO CZYSZCZENIA KOLEJNEGO PLIKU!!!!!!!! PRAWDOPODOBNIE NA WYJSCIU FORA //BĘDZIE TRZEBA DODAĆ PRZYPISYWANIE LISTY DO DANEJ TABLICY ORAZ WYKONAĆ CZYSZCZENIE LISTY TAK BY KOLEJNE 10000 PRÓBEK ZAPISYWAŁO SIE OD NOWA (OSZCZĘDNOŚC PAMIĘCI) //ZASTANOWAIĆ SIĘ CZY NIE BĘDZIE KONIECZNE WRZUCENIE NA KONIEC BOOLEANÓW TAK BY STATUSY DZIAŁAŁY POPRAWNIE NAWET JAK UŻYTKOWNIK NIE WYJDZIE Z PROGRAMU }