APL solution to change pitch of a WAV file by semitones?

APL-related discussions - a stream of APL consciousness.
Not sure where to start a discussion ? Here's the place to be
Forum rules
This forum is for discussing APL-related issues. If you think that the subject is off-topic, then the Chat forum is probably a better place for your thoughts !

APL solution to change pitch of a WAV file by semitones?

Postby Rav on Tue Jul 23, 2024 2:24 pm

I'm looking for a Dyalog APL-only solution to change the pitch of a WAV file by one or more semitones. Specifically, one or more semi-tones higher. I don't want to use external libraries or executables, .NET, etc. However, Win32 functions are OK if needed. Does anyone have anything written in Dyalog APL to do that? Thanks. / Rav
Rav
 
Posts: 23
Joined: Tue Jul 25, 2023 4:41 pm

Re: APL solution to change pitch of a WAV file by semitones?

Postby ray on Wed Jul 24, 2024 3:47 pm

Hi Rav

I have code to produce WAV files. Both simple (sine, square , sawtooth etc) single frequencies, and complex music (with virtual instruments Church Organ, Piano and Guitar).

This code "knows" about the format of WAV file.

Note: WAV files (unlike say MIDI files) do not contain frequency data, but thousands of "samples" (frequently 44100 samples taken each second) taken from the sound, irrespective of the pitch.

I assume that given a "tune" in a WAV file, you wish to create a new WAV file with that "tune" transposed into another "key".

You could transpose the music part of a MONO WAV file by one octave by simply extracting the musical "data" part, deleting every other "sample", and writing out a new WAV file with this data, with minor adjustments to the header data relating to the change in size.

If the source was a Stereo file, you would need to remove every other "pair" of samples.

To change it by a Semi-tone, I therefor assume you would need to remove every twelfth sample (or sample pair), rather than every other.

However, the discontinuities introduced could be severe at some frequencies, so I am very unsure if this would work when working with instruments with many harmonics, rather than just a simple sine wav.


For information about creating simple WAV files, and their format see
https://github.com/RayCannon/Wave

Here is my MakeWave function
Code: Select all
 name MakeWave freq;file;tie;chans;rate;bits;size;max;sizpos;datpos;count;data;cycle;halfcycle;unit;style;note;notes;dur;durs;part
 ⍝;file;tie;sizpos;datpos;cycle;val;size;freq;rate;bits;chans;chan;max;count;bin;data
 ⍝ Make 1 cycle of wave sound

 file←'C:\Program Files\APL\notes\',name,'.wav'
 tie←MakeFile file

⍝ Write the ".WAV" header
⍝  1 - 4   “RIFF”   Marks the file as a riff file. Characters are each 1 byte long.
 sizpos←tie WriteChar'RIFF' ⍝  RIFF format tag

⍝  5 - 8   File size (integer)   Size of the overall file - 8 bytes, in bytes (32-bit integer).
⍝        Typically, you’d fill this in after creation using sizpos.
 tie WriteLong 0            ⍝  Size to end of file from here, fill in later

⍝  9 -12   “WAVE”   File Type Header. For our purposes, it always equals “WAVE”.
 tie WriteChar'WAVE'        ⍝  .WAV tag

⍝  13-16   “fmt "   Format chunk marker. Includes trailing null
 tie WriteChar'fmt '        ⍝  Format tag

⍝  17-20   "16"    Length of format data as listed above
 tie WriteLong 16           ⍝  Size of Format  will be 0x10 (16 bytes)

⍝  21-22   "1"     Type of format (1 is PCM) - 2 byte integer
 tie WriteShort 1           ⍝  Format tag WAVE_FMT_PCM

⍝  23-24   "2"   Number of Channels - 2 byte integer
 chans←1                    ⍝  Mono=1/Stero=2
 tie WriteShort chans       ⍝  number of channels

⍝  25-28   "44100"   Sample Rate - 32 byte integer.
⍝  Common values are 44100 (CD), 48000 (DAT).
⍝  Sample Rate = Number of Samples per second, or Hertz.
 rate←44100                 ⍝  low=8000/high=44100 sample rate in Bytes per sec
 tie WriteLong rate         ⍝  Number of samples a second

⍝  29-32   "176400"   (Sample Rate * BitsPerSample * Channels) / 8.
 bits←16                     ⍝  size (in bits) of a sample
 size←rate×(bits÷8)×chans   ⍝  size (in bytes) of 1 sec of sound
 tie WriteLong size         ⍝  Number of Bytes per sec

⍝  33-34   "4"   (BitsPerSample * Channels) / 8.
⍝         1 - 8  bit mono
⍝         2 - 8  bit stereo 16 bit mono
⍝         4 - 16 bit stereo
 unit←(bits×chans)÷8
 tie WriteShort unit        ⍝  size per unit

⍝  35-36   "16"   Bits per sample
 tie WriteShort bits        ⍝  Number of bits in each sample

⍝  37-40   “data”   Data chunk header.
⍝         Marks the beginning of the data section.
 datpos←tie WriteChar'data' ⍝  Data Block tag

⍝  41-44   File size (data)   Size of the data section.
 tie WriteLong 0            ⍝  Size of data the following data, fill in later

⍝ Data section follows
 count←0
 style←⊃0 0 1 0 0/'sine' 'triangle25' 'square' 'sawtooth' 'isosceles'
 data←style MakeData(chans size rate bits freq)
 :For cycle :In 1        ⍝ Number of complete cycles to write to file
     :If bits=16
         tie WriteShort data
         count+←2×⍴data
     :Else
         tie WriteSmall data
         count+←⍴data
     :End
 :End
 ⍝ update the header with the correct sizes
 tie sizpos ReWriteLong count+40 ⍝ 40 is 28 in hex
 tie datpos ReWriteLong count
 ⎕NUNTIE tie


The sub-function (WriteLong, WriteShort, WriteSmall,etc)are defined in the GitHub.

I hope this helps.
Ray
Ray Cannon
Please excuse any smelling pisstakes.
User avatar
ray
 
Posts: 238
Joined: Wed Feb 24, 2010 12:24 am
Location: Blackwater, Camberley. UK

Re: APL solution to change pitch of a WAV file by semitones?

Postby ray on Wed Jul 24, 2024 4:14 pm

Rav,

I have just tried creating a wave file one semitone higher, by deleting every twelfth sample, and it appears to have worked OK.

Good luck

Ray
Ray Cannon
Please excuse any smelling pisstakes.
User avatar
ray
 
Posts: 238
Joined: Wed Feb 24, 2010 12:24 am
Location: Blackwater, Camberley. UK

Re: APL solution to change pitch of a WAV file by semitones?

Postby Rav on Wed Jul 24, 2024 10:21 pm

Thank you, Ray! Success! I studied your MakeWave function and then spent a few hours writing a function to generate the next 12 semi-tone WAV files from the my "original tone" WAV file. Previously, I had used Audacity to create those 12 files and was reading them in and storing them in my workspace, which was taking a lot of room (13 times the space needed for the original tone file). Now I only have to store the one original tone file, and generate the other 12 files on the fly, and it's very fast. My workspace size is now about half the size it used to be, and that will make loading and running the workspace faster for my end user.

I had to manually figure out how many pairs of samples to remove for each semi-tone (my original file is in stereo). Most of the semi-tone files were derived from the original tone file, while others were derived from some of the derived tone files. I hope that makes sense.

The quality of the derived tone files isn't quite as good what Audacity produced, but it's good enough for what I'm using it for. I imagine that Audacity is changing the pitch in some other way.

Anyway, again thanks very much. / Rav
Rav
 
Posts: 23
Joined: Tue Jul 25, 2023 4:41 pm


Return to APL Chat

Who is online

Users browsing this forum: No registered users and 1 guest