{"id":94,"date":"2024-02-17T12:04:58","date_gmt":"2024-02-17T20:04:58","guid":{"rendered":"https:\/\/www.kerneltrick.com\/?p=94"},"modified":"2024-02-17T12:09:51","modified_gmt":"2024-02-17T20:09:51","slug":"time-stretch-audio-and-preserving-pitch","status":"publish","type":"post","link":"https:\/\/www.kerneltrick.com\/?p=94","title":{"rendered":"Time Stretch audio and preserving pitch"},"content":{"rendered":"<p>Here is a example program that will take an audio sample, stretch it out in time but will preserve the pitch. It&#8217;s based loosely on paulstretch. <br \/>Written in BlitzMax NG.\u00a0 The program is written for simplicity not speed.<\/p>\r\n<p>The important variables are:<\/p>\r\n<p>ScaleFactor : how much to stretch the audio, a value of 3 would be 3 times the original length. <br \/>WindowLen: Larger makes a cleaner sound but is slower to run. Must be a power of 2. <br \/>Number of threads to run: This should less that the number of logical cores on your cpu.<\/p>\r\n<p>Hit escape key to exit the program. Download windows exe, sourcecode and included audio file <a href=\"https:\/\/www.kerneltrick.com\/download\/TimeStretchAudioExample.zip\">HERE<\/a> <a href=\"https:\/\/www.kerneltrick.com\/?attachment_id=95\" rel=\"attachment wp-att-95\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-95\" src=\"https:\/\/www.kerneltrick.com\/wp-content\/uploads\/2024\/02\/TimeStretchAudio1.png\" alt=\"\" width=\"801\" height=\"626\" srcset=\"https:\/\/www.kerneltrick.com\/wp-content\/uploads\/2024\/02\/TimeStretchAudio1.png 801w, https:\/\/www.kerneltrick.com\/wp-content\/uploads\/2024\/02\/TimeStretchAudio1-300x234.png 300w, https:\/\/www.kerneltrick.com\/wp-content\/uploads\/2024\/02\/TimeStretchAudio1-768x600.png 768w\" sizes=\"auto, (max-width: 801px) 100vw, 801px\" \/><\/a><\/p>\r\n\r\n<pre class=\"wp-block-code has-small-font-size\" title=\"Source Code (BlitzmaxNG)\"><code class=\"language-visual-basic line-numbers\" lang=\"visual-basic\">\r\n'\/\/ Time stretch audio roughly based on paulstretch by A.Woodard [aaron at kerneltrick.com]\r\n\r\nSuperStrict\r\nImport brl.glmax2d\r\nImport brl.threadpool\r\nImport pub.freeaudio\r\nImport brl.audio\r\nImport brl.audiosample\r\nImport brl.wavloader\r\nImport brl.oggloader\r\n\r\nAppTitle = \"Timestretch audio in BlitzMax\"\r\n\r\nType TTask Extends TRunnable\r\n\tGlobal mutex:TMutex = TMutex.Create()\r\n\tField Readpos:Int, Writepos:Int\r\n\tMethod New(Rp:Int, Wp:Int)\r\n\t\tReadPos = Rp\r\n\t\tWritepos = Wp\r\n\t\tPrint(\"task::new\")\r\n\tEnd Method\r\n\t\r\n\tMethod run()\r\n\t\tDoChunk(ReadPos, writepos, mutex)\r\n\t\tDelay 2\r\n\tEnd Method\r\nEnd Type\r\n\r\nSeedRnd(MilliSecs())\r\n\r\nConst TWOPI:Double = Pi * 2\r\nConst GW:Int = 800\r\nConst GH:Int = 600\r\nConst OrigAudioFile:String = \"dhm.wav\"\r\n\r\nGlobal Tpool:TThreadPoolExecutor = TThreadPoolExecutor.newFixedThreadPool(4)  '\/\/ Num threads should be less than cpu cores\r\nGlobal Origdata:Float[]\r\nGlobal ScaleFactor:Float = 4\t\t\t\t\t\t\t\t\t\t\t\t\t'\/\/ How much to stretch the audio\r\nGlobal Newdata:Float[(Origdata.length * scalefactor) + 1]\r\nGlobal WindowLen:Float = 1024 * 4\t\t\t\t\t\t\t\t\t\t\t\t'\/\/ Window size, larger sounds better but is slower\r\nGlobal Offset:Float = (WindowLen \/ 2) \/ (ScaleFactor)\r\nGlobal ReadPos:Int = 0\r\nGlobal N:Int = windowlen\r\nGlobal orig:TAudioSample\r\n\r\nGraphics GW, GH,0,10\r\n\r\nDoStretch()\r\n\r\nWhile Not KeyHit(KEY_ESCAPE)\r\n\tCls\r\n\t\tDrawData()\r\n\t\t'DrawText(Tpool.threadCount + \" \" + Tpool.threadsWorking, 10, 10)\r\n\t\t\r\n\t\tIf KeyHit(KEY_1) And Tpool.threadsWorking = 0 Then\r\n\t\t\tPlaySound(LoadSound(orig))\r\n\t\tEndIf\r\n\t\t\r\n\t\tIf KeyHit(KEY_2) And Tpool.threadsWorking = 0 Then\r\n\t\t\tLocal newdat:Short[Newdata.length]\r\n\t\t\tFor Local i:Int = 0 Until newdat.Length\r\n\t\t\t\tnewdat[i] = Newdata[i] * 32767\r\n\t\t\tNext\r\n\t\t\tLocal newsamp:TAudioSample = New TAudioSample()\r\n\t\t\tnewsamp.format = SF_MONO16LE\r\n\t\t\tnewsamp = newsamp.CreateStatic(Varptr newdat[0], newdat.Length, 44100, SF_MONO16LE)\r\n\t\t\tPlaySound(LoadSound(newsamp))\r\n\t\tEnd If\r\n\t\t\r\n\t\tIf Tpool.threadsWorking = 0 Then\r\n\t\t\tDrawText(\"Press 1 to play original sound, Press 2 to play stretched sound\", 10, 10)\r\n\t\tElse\r\n\t\t\tDrawText(\"Working, wait....\", 10, 10)\r\n\t\tEndIf\r\n\t\t\r\n\tFlip\r\nWend\r\n\r\n\r\n'-------------------------------------------------------------------------------------------------\r\nFunction DoStretch()\r\n\tLocal cPos:Int\r\n\tLocal count:Int = 0\r\n\tIf Not(Not (WindowLen &amp; (WindowLen - 1))) Then Notify(\"windowlen must be pow\/2\", 1) ;End\r\n\torig:TAudioSample = LoadAudioSample(OrigAudioFile)\t'\/\/ Assumes Mono. Using ogg sample beacuse this func hates .wav\r\n\tIf Not orig Then Notify(\"Cant load audio\", 1) ;End\r\n\torig = orig.Convert(SF_MONO16LE)\r\n\tOrigdata = New Float[orig.Length]\r\n\tLocal p:Short Ptr = Short Ptr(orig.samples)\r\n\t\r\n\tFor Local i:Int = 0 Until orig.Length\r\n\t\tOrigdata[i] = 0.8 * ((Float(Short(32767 - p[0])) \/ 32767.0) - 1.0) \r\n\t\tp:+1\r\n\tNext\r\n\t\r\n\tNewdata = New Float[(Origdata.Length * ScaleFactor) + 1]\r\n\t\r\n\tWhile ReadPos + WindowLen &lt; Origdata.Length\r\n\t\tCPos = ((N \/ 2) * Count)\r\n\t\t\r\n\t\tTpool.execute(New TTask(ReadPos, CPos))\r\n\t\t\r\n\t\tCount:+1\r\n\t\tReadPos:+Offset\r\n\tWend\r\nEnd Function\r\n\r\n\r\n'-------------------------------------------------------------------------------------------------\r\nFunction DoChunk(Readpos:Int, Writepos:Int, mut:TMutex)\r\n\tPrint \"HERE  \" + ReadPos + \" \" + Writepos\r\n\tLocal cp:Float[N]\r\n\tLocal sp:Float[N]\r\n\tLocal SpecBuff:Float[]\r\n\tLocal obuff:Float[]\r\n\tLocal isr:Float = 0.853\r\n\t\r\n\tSpecBuff = Origdata[Readpos..(Readpos + N)]\r\n\t\r\n\tFor Local i:Int = 0 Until N\r\n\t\tSpecBuff[i]:*Sine(Pi * i \/ (N))\r\n\tNext\r\n\tSpeccy(SpecBuff, -1, cp, sp)\r\n\tcp[0] = 0\r\n\tsp[0] = 0\r\n\tFor Local i:Int = 0 Until N \/ 2\r\n\t\tcp[i] = Sqr(cp[i] * cp[i] + sp[i] * sp[i])\r\n\t\tLocal p:Float = Rnd(-Pi, Pi)\r\n\t\tsp[i] = cp[i] * Sine(p)\r\n\t\tcp[i] = cp[i] * Cosine(p)\r\n\tNext\r\n\t\r\n\tSpeccy(SpecBuff, 1, cp, sp)\r\n\t\r\n\tFor Local i:Int = 0 Until N\r\n\t\tSpecBuff[i]:*Sine(Pi * i \/ N)\r\n\tNext\r\n\t\r\n\tmut.Lock()\t\r\n\t\tFor Local i:Int = 0 Until N '\/ 2\r\n\t\t\tNewdata[i + Writepos]:+(SpecBuff[i])\t\t\r\n\t\t\tNewdata[i + Writepos]:*(isr - (1.0 - isr) * Sine(Pi * i \/ N))\r\n\t\tNext\r\n\t\t\r\n\t\tFor Local i:Int = 0 Until N\r\n\t\t\tNewdata[i + Writepos] = ClampOne(Newdata[i + Writepos])\r\n\t\tNext\r\n\tmut.Unlock\r\n\t\r\n\tFunction Speccy(buff:Float[], s:Int = -1, cp:Float[], sp:Float[])\t'\/\/ Insanely slow !!!\r\n\t\tIf s = 1 Then\r\n\t\t\tMemClear(Varptr buff[0], SizeOf(1:Float) * N)\t\r\n\t\t\tFor Local k:Int = 0 Until N\r\n\t\t\t\tFor Local b:Int = 0 Until N \/ 2\r\n\t\t\t\t\tLocal a:Float = 2.0 * b * Pi * k \/ N\r\n\t\t\t\t\tbuff[k]:+(cp[b] * Cosine(a)) + ((-sp[b]) * Sine(a))\r\n\t\t\t\tNext\r\n\t\t\t\tbuff[k] = 2 * buff[k] \/ N\r\n\t\t\tNext\r\n\t\tElse\r\n\t\t\tMemClear(Varptr cp[0], SizeOf(1:Float) * N)\r\n\t\t\tMemClear(Varptr sp[0], SizeOf(1:Float) * N)\r\n\t\t\tFor Local k:Int = 0 Until N\t\t\r\n\t\t\t\tFor Local b:Int = 0 Until N \/ 2\r\n\t\t\t\t\tLocal a# = 2.0 * b * Pi * k \/ N\r\n\t\t\t\t\tsp[b]:+Buff[k] * s * Sine(a)\r\n\t\t\t\t\tcp[b]:+buff[k] * Cosine(a)\t\t\r\n\t\t\t\tNext\r\n\t\t\tNext\r\n\t\tEnd If\r\n\tEndFunction\r\nEnd Function\r\n'-------------------------------------------------------------------------------------------------\r\nFunction DrawData()\r\n\tLocal x:Float\r\n\tLocal xs:Float = GW \/ Float(Newdata.length)\r\n\tFor Local i:Int = 0 Until Origdata.Length\r\n\t\tDrawRect(x, 200 + Origdata[i] * 100, 2, 1)\r\n\t\tx:+xs\r\n\tNext\r\n\tx = 0\r\n\tFor Local i:Int = 0 Until Newdata.Length\r\n\t\tDrawRect(x, 400 + Newdata[i] * 100, 2, 1)\r\n\t\tx:+xs\r\n\tNext\r\n\tSetColor 255, 0, 255\r\n\tDrawText(\"Original\", 50, 200 - 5)\r\n\tDrawText(\"Streched\", 50, 400 - 5)\r\n\tSetColor 255, 255, 255\r\n\t'DrawRect(0, 200, 1000, 1)\r\n\tDelay 10\r\nEnd Function\r\n\r\n'-------------------------------------------------------------------------------------------------\r\nFunction Sine:Double(x:Double) inline\r\n    Local k:Int;Local y:Double;Local z:Double\r\n    z = x\r\n    z:*0.3183098861837907\r\n    z:+6755399441055744.0\r\n    k = Int Ptr(Varptr(z))[0]\r\n    z = k\r\n    z:*3.1415926535897932\r\n    x:-z;y = x;y:*x\r\n    z = 0.0073524681968701\r\n    z:*y\r\n    z:-0.1652891139701474\r\n    z:*y\r\n    z:+0.9996919862959676\r\n    x:*z;k:&amp;1;k:+k;z = k;z:*x;x:-z\r\n    Return x\r\nEnd Function\r\n\r\n'-------------------------------------------------------------------------------------------------\r\nFunction Cosine:Double(x:Double) Inline\r\n\tReturn Sine((3.1415926535897932 \/ 2.0) - x)\r\nEnd Function\r\n'-------------------------------------------------------------------------------------------------\r\nFunction ClampOne:Double(x:Double) Inline\r\n\tReturn 0.5 * (Abs(x + 1) - Abs(x - 1))\r\nEnd Function\r\n'-------------------------------------------------------------------------------------------------\r\n\r\n\r\n\r\n<\/code><\/pre>\r\n","protected":false},"excerpt":{"rendered":"<p>Here is a example program that will take an audio sample, stretch it out in time but will preserve the pitch. It&#8217;s based loosely on paulstretch. Written in BlitzMax NG.\u00a0 The program is written for simplicity not speed. The important variables are: ScaleFactor : how much to stretch the audio, a value of 3 would&#8230; <\/p>\n<div class=\"read-more navbutton\"><a href=\"https:\/\/www.kerneltrick.com\/?p=94\">Read More<i class=\"fa fa-angle-double-right\"><\/i><\/a><\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21,6,1],"tags":[],"class_list":["post-94","post","type-post","status-publish","format-standard","hentry","category-audio","category-programming","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.kerneltrick.com\/index.php?rest_route=\/wp\/v2\/posts\/94","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.kerneltrick.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.kerneltrick.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.kerneltrick.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.kerneltrick.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=94"}],"version-history":[{"count":0,"href":"https:\/\/www.kerneltrick.com\/index.php?rest_route=\/wp\/v2\/posts\/94\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.kerneltrick.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=94"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.kerneltrick.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=94"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.kerneltrick.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=94"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}