WaveBeans / wavebeans

Audio Processing. On scale.
https://wavebeans.io
Apache License 2.0
24 stars 0 forks source link

Writing modified data to WAV file. #80

Closed cristianc1997 closed 3 years ago

cristianc1997 commented 3 years ago

Hello! First of all, thank you for this project. I am currently using wavebeans for a personal Android project to read and modify wav files.

However, I am having issues with writing to file.

I read the file as such:

data = wave(uri).map { it.asDouble() }
            .asSequence(sampleRate)
            .toList()
            .toDoubleArray()

Thus, all modifications made to the data are stored inplace in the same DoubleArray. My issue comes down to saving the file. I am unsure how to write to the appropriate MonoWav file, and the examples provided are not clear on how to achieve this with an array of data, as opposed to generating and saving a sine wave.

I have the sampleRate, the data and the encoding, but I do not know how to write to a Wav file.

Thank you for your time.

asubb commented 3 years ago

Hi @cristianc1997,

Thank you for giving a try to the project on such early stages. Honestly, never tried to use that library on Android, so it would be nice to know more about your experience. You're right, the documentation is hard to read nowadays, I definitely need to spend more time on generating more examples.

To your question.

There is a concept of Outputs, where you can define storing the stream into a wav-file: https://wavebeans.io/docs/api/outputs/wav-output.html. So in your case that would look like it:

val output = wave(uri)
       /* .map {  } // do some handling, the result should be of Sample type (typealias for Double now but still) */
       .toMono16bitWav("file:///path/to/file.wav") // or choose the bitrate you need out of 8, 16, 24, 32

But creating output only won't help you, you would need to actually execute it. You may read more about execution here, but I may suggest to use the Writer approach to start with. The code would look like this:

val writer = output.writer(sampleRate)
while (writer.write()) {  }
writer.close()

Let me know if that helps with your problem.

cristianc1997 commented 3 years ago

Hello @asubb,

I have tried to do something similar. My issue consists of creating the stream itself, rather than the process of writing to file per se. This is what I've attempted: and I wish to reiterate that my data variable consists of the DoubleArray that was initially obtained as seen in the initial post:

val stream = (data.map{ sampleOf(it) }.asSequence() as BeanStream<Sample>).toMono16bitWav(path)
stream.writer(sampleRate).use {
            while(it.write()){/* wait */}
        }

However, I cannot cannot cast this sequence to a BeanStream, the application crashing due to an impossible cast.

While I can obtain a Stream, I am unsure as to how to obtain the BeanStream, which results in me being able to call .toMono16bitWav;

As far as what you've shown me above,


val output = wave(uri)
       /* .map {  } // do some handling, the result should be of Sample type (typealias for Double now but still) */
       .toMono16bitWav("file:///path/to/file.wav") // or choose the bitrate you need out of 8, 16, 24, 32

I am not sure I understand. Should I temporarily write my DoubleArray to a CSV and then reopen it?

I've already read the .wav file once, stored the data in a DoubleArray(as shown in the inital post) and then made some modifications to the data in the DoubleArray. I am now attempting to write this DoubleArray to a wav file (given I already have the samplerate and encoding).

Otherwise, I've yet to encounter any issues with the project on Android. It's worked very well and fast.

asubb commented 3 years ago

@cristianc1997,

Firstly, let me ask you the question, why you need to read the stream into a DoubleArray? The reason I'm asking is that the intent of the library mainly to process the wave signal similar to Kotlin sequence keeping only data that is needed for processing. without using interim buffers and therefore memory.

Though, if that is essential and inevitable, I would suggest to use list as input: https://wavebeans.io/docs/api/inputs/list-as-input.html

val data: DoubleArray = /*....*/

val output = data.map{ sampleOf(it) }.input()
   .toMono16bitWav(path)

// evaluate output here
cristianc1997 commented 3 years ago

@asubb

I understand.

I have read the stream into a DoubleArray as I am plotting the the wave signal in both time and frequency domain. As part of the application, I also apply different audio effects (ie echo, reverberation) to the file. I am by no means proficient in Kotlin/Android, and thus far, I figured that the 'easiest' solution would be to store the data after reading it, process it as required, and then write it to a new file.

As such, while I understand that I'm not using the full potential of the library, I've yet to figure out how to use the streams for what I'm personally trying to do (it is my first project using Kotlin and by extension, Android). If you have any pointers as to how to make use of streams and the full potential of the library, I am more than open to learning.

Otherwise, what you've provided me above is indeed what I was looking for. Thank you very much!

asubb commented 3 years ago

@cristianc1997,

I would really love to go over your task and try to put it on top of the framework as it would be required, and perhaps even develop the API to better serve the needs.

It would be nice if at some point you share a piece of code or a repo, if that's not something that you need to keep only to yourself due to different circumstances, and I would spend some time trying.

Thank you for your feedback and your effort!