Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save a selection as a new file #526

Closed
Joshfindit opened this issue Sep 11, 2015 · 12 comments
Closed

Save a selection as a new file #526

Joshfindit opened this issue Sep 11, 2015 · 12 comments
Labels

Comments

@Joshfindit
Copy link

I've looked through the documentation as well as the issues, and can't see anywhere that talks about saving a file from wavesurfer.

Is there currently a built-in way to do this?

What I'm looking for in the end is to:

  1. load an mp3
  2. select an area
  3. press a Save button and be asked where to save the new file (with a code-generated filename already filled in).
@katspaugh
Copy link
Owner

Duplicate of #419.

@Joshfindit
Copy link
Author

I feel like the pieces are there, but am not versed in blobs.
Are there any tutorials or documentation that cover how it could be done with wavesurfer?

Specifically, the code in #419 says that it allows a user to select an area, then it returns a blob containing the audio in that area; how would someone save that audio clip to the user's disk?

@katspaugh
Copy link
Owner

So, when you create a region, you have its start and end positions in seconds. In Web Audio, the audio track (when decoded) is represented as an array of floats. To copy your region, you need to know where in the array it starts and ends–in frames, not in seconds. To convert seconds to frames, you just multiply seconds by the sample rate with which the audio was decoded.

In the original example we copy the whole audio into a new buffer (because j = 0 in the inner loop), but you need to iterate from the start frame up to the end frame.

var originalBuffer = wavesurfer.backend.buffer;
var emptySegment = wavesurfer.ac.createBuffer(
    originalBuffer.channels,
    segmentDuration * originalBuffer.sampleRate,
    originalBuffer.sampleRate
);
for (var i = 0; i < originalBuffer.channels; i++) {
    var chanData = originalBuffer.getChannelData(i);
    var segmentChanData = emptySegment.getChannelData(i);
    for (var j = 0, len = chanData.length; j < len; j++) {
        segmentChanData[j] = chanData[j];
    }
}

emptySegment; // Here you go! Not empty anymore, contains a copy of the segment!

Hopefully this helps.

@Joshfindit
Copy link
Author

I've been looking this over for a little while, and while I understand the process of copying frame by frame from the original buffer in to an empty buffer, wavesurfer.ac.createBuffer is not defined. (in fact, wavesurfer.ac is not defined)

As for the saving, would it be correct to say that once the buffer is full, another tool will be needed to parse the buffer in to a file (WAV, but ideally mp3)?

@thijstriemstra
Copy link
Contributor

It's wavesurfer.backend.ac if I remember correctly.

@Joshfindit
Copy link
Author

Cheers.
Changed the code to reflect the current state of things.
Any thoughts on saving this to a WAV or mp3?

var segmentDuration = wavesurfer.backend.buffer.duration;
var originalBuffer = wavesurfer.backend.buffer;
var emptySegment = wavesurfer.backend.ac.createBuffer(
    originalBuffer.numberOfChannels,
    segmentDuration * originalBuffer.sampleRate,
    originalBuffer.sampleRate
);
for (var i = 0; i < originalBuffer.channels; i++) {
    var chanData = originalBuffer.getChannelData(i);
    var segmentChanData = emptySegment.getChannelData(i);
    for (var j = 0, len = chanData.length; j < len; j++) {
        segmentChanData[j] = chanData[j];
    }
}

emptySegment; // Here you go! Not empty anymore, contains a copy of the segment!

@Joshfindit Joshfindit changed the title Save a selection Save a selection as a new file Oct 6, 2015
@designscripting
Copy link

How can we convert the emptySegment in above code to blob??
Please help us..

@designscripting
Copy link

Have tried below code but, the result mp3 is not playing, any suggestion?

var blob = new window.Blob([new Uint8Array(emptySegment)], { type: 'audio/mpeg'});
var url = URL.createObjectURL(blob);
var li = document.createElement('li');
var au = document.createElement('audio');
var hf = document.createElement('a');
au.controls = true;
au.src = url;
hf.href = url;
hf.download = new Date().toISOString() + '.mp3';
hf.innerHTML = hf.download;
li.appendChild(au);
li.appendChild(hf);
document.getElementById("myList").appendChild(li);

console.log('done encoding');

@LBPSlava
Copy link

This thread ends inconclusively, and I would like to produce what Joshfindit originally intended: a simple sound trimmer or editor based on wavesurger's ability to define clips visually. Or has this been done elsewhere?

@erik-iglikov
Copy link

Facing exactly the same issue, did you figure it out?

I've tried to use region's start and end and use ffmpeg.wasm to cut the region. Also tried to use getChannelData(0) and build either a Blob or File object and export it, but unsuccessfully.

@LBPSlava
Copy link

LBPSlava commented Jul 5, 2022 via email

@StudentSA
Copy link

I know I'm a bit late to the party but if it helps someone else in the future he goes:

I used lamejs as the encoder library that can be included as a js in your page:

<script type="text/javascript" src="./js/lame.all.js"></script>

wavesurfer stores the actual audio data in its buffer variable: wavesurfer.backend.buffer

We need to create our own samples buffer by selecting the samples from the wavesurfer main buffer.

  start = 10; // seconds from which to start the clip
  end = 30; // seconds from which to end the clip
  channels = wavesurfer.backend.buffer.numberOfChannels;
  sampleRate = wavesurfer.backend.buffer.sampleRate;
  kbps = 40; //the mp3 kbps that you want your clipped file to be 

  mp3encoder = new lamejs.Mp3Encoder(channels, sampleRate, kbps);
  var mp3Data = [];
  from_sample = start*sampleRate;
  to_sample = end*sampleRate;
  samples_float32 = wavesurfer.backend.buffer.getChannelData(0).slice(from_sample,to_sample);
  var norm_samples = samples_float32.map(function(x) { return x * 0xFFFF; });
  samples = new Int16Array(norm_samples);

  sampleBlockSize = 1152;
  for (var i = 0; i < samples.length; i += sampleBlockSize) {
      sampleChunk = samples.subarray(i, i + sampleBlockSize);
      var mp3buf = mp3encoder.encodeBuffer(sampleChunk);
      if (mp3buf.length > 0) {
          mp3Data.push(mp3buf);
      }
  }
  var mp3buf = mp3encoder.flush();
  if (mp3buf.length > 0) {
    mp3Data.push(new Int8Array(mp3buf));
  }

  var blob = new Blob(mp3Data, {type: 'audio/mp3'});
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = "clip.mp3";
  link.click();

The trick seems to have been that the audio samples to export need to be in 16bit array whereas wavesurfer uses a float32 array.

The above example is also hardcoded for mono so please note some adjustments are needed to support stereo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants