{"id":17196591,"url":"https://github.com/johnnovak/easywave","last_synced_at":"2025-04-09T16:17:46.666Z","repository":{"id":70773165,"uuid":"143413511","full_name":"johnnovak/easywave","owner":"johnnovak","description":"Easy WAVE file handling for Nim","archived":false,"fork":false,"pushed_at":"2024-03-06T11:18:05.000Z","size":78,"stargazers_count":20,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-09T16:17:38.503Z","etag":null,"topics":["nim","nim-lang","wav","wave"],"latest_commit_sha":null,"homepage":"","language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"wtfpl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnnovak.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-03T10:25:22.000Z","updated_at":"2024-09-12T13:19:39.000Z","dependencies_parsed_at":"2025-01-30T07:27:03.426Z","dependency_job_id":"ae5e4e92-4427-4398-9bff-b8358d14ac98","html_url":"https://github.com/johnnovak/easywave","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnnovak%2Feasywave","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnnovak%2Feasywave/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnnovak%2Feasywave/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnnovak%2Feasywave/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnnovak","download_url":"https://codeload.github.com/johnnovak/easywave/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248065282,"owners_count":21041872,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nim","nim-lang","wav","wave"],"created_at":"2024-10-15T01:53:41.436Z","updated_at":"2025-04-09T16:17:46.637Z","avatar_url":"https://github.com/johnnovak.png","language":"Nim","funding_links":[],"categories":[],"sub_categories":[],"readme":"# easyWAVE\n\n**Work in progress, not ready for public use yet!**\n\n## Overview\n\n**easyWAVE** is a native Nim library that supports the reading and writing of\nthe most common subset of the WAVE audio file format. Only uncompressed PCM\ndata is supported (which is used 99.99% of the time in the real world).  The\nlibrary does not abstract away the file format; you'll still need to have some\nunderstanding of how WAVE files are structured to use it.\n\nThe WAVE format is not complicated, but there are lots of little details that\nare quite easy to get wrong. This library gives you a toolkit to read and\nwrite WAVE files in a safe and easy manner—most of the error prone and tedious\nstuff is handled by the library (e.g. chunk size calculation when writing\nnested chunks, automatic padding of odd-sized chunks, transparent byte-order\nswapping in I/O methods etc.)\n\n### Features\n\n* Reading and writing of **8/16/24/32-bit integer PCM** and **32/64-bit IEEE float PCM** WAVE files\n* Reading and writing of **markers** and **regions**\n* An easy way to write **nested chunks**\n* Support for **little-endian (RIFF)** and **big-endian (RIFX)** files\n* Works on both little-endian and big-endian architectures (byte-swapping is\n  handled transparently to the client code)\n* Native Nim implementation, no external dependencies\n* Released under [WTFPL](http://www.wtfpl.net/)\n\n### Limitations\n\n* No support for compressed formats\n* No support for esoteric bit-lengths (e.g. 20-bit PCM)\n* Can only read/write the format and cue chunks, and partially the\n  list chunk. Reading/writing of any other chunk types has to be implemented\n  by the user.\n* No direct support for editing (updating) existing files\n* No \"recovery mode\" for handling malformed files\n* Only file I/O is supported (so no streams or memory buffers)\n\n## Installation\n\nThe best way to install the library is by using `nimble`:\n\n```\nnimble install easywave\n```\n\n## Usage\n\n### Reading WAVE files\n\nReading WAVE files is accomplished through `WaveReader` objects.\nA `WaveReaderError` will be raised if an I/O error was encountered or if the\nWAVE file is invalid.\n\n#### Basic usage\n\nJust call `parseWaveFile()` with the filename of the WAVE file. You can set\nthe `readRegions` option to `true` if you're interested in the markers/regions\nstored in the file as well.\n\nThis method will:\n\n* Parse the WAVE file headers and the **format chunk** (`\"fmt \"`). Information\n  about the sample format will be available via the `endianness`, `format`,\n  `sampleRate` and `numChannels` properties.\n\n* Find all chunks in the file and store this info as a sequence of\n  `ChunkInfo` objects in the `chunks` property. The size of the sample\n  data in bytes will be available through the `dataSize` property.\n\n* If `readRegions` was set to `true`, try to read marker and region\n  info from the **cue** (`\"cue \"`) and **list chunks** (`\"LIST\"`).\n\n* Set the file pointer to the start of the sample data in the **data chunk**\n  (`\"data\"`).\n\nA simple example that illustrates all these points:\n\n```nimrod\nimport strformat, tables\nimport easywave\n\nvar wr = parseWaveFile(\"example.wav\", readRegions = true)\n\necho fmt\"Endianness: {wr.endianness}\"\necho fmt\"Format:     {wr.format}\"\necho fmt\"Samplerate: {wr.sampleRate}\"\necho fmt\"Channels:   {wr.numChannels}\"\n\nfor ci in wr.chunks:\n  echo ci\n\nif wr.regions.len \u003e 0:\n  for id, r in wr.regions.pairs:\n    echo fmt\"id: {id}, {r}\"\n\nvar numBytes = wr.dataSize\necho fmt\"Sample data size: {numBytes} bytes\"\n\n# File pointer is now at the start of the sample data\n```\n\nReading single values or chunks of data from the file is accomplished through\nthe various `read*` methods. See the API docs for the full list. It's your\nresponsibility to ensure that you read the sample data with the appropriate\nread method; there's nothing stopping you from reading 16-bit integer data as\n64-bit floats, for example, if that's what you really want\n:stuck_out_tongue_winking_eye:\n\n```nimrod\n# Single value read\nlet v3 = wr.readInt8()\nlet v1 = wr.readUInt16()\nlet v2 = wr.readFloat32()\n\n# Buffered read\nvar buf16: array[4096, int16]\nwr.readData(buf16)            # read until the buffer is full\n\nvar buf32float = newSeq[float32](1024)\nwr.readData(buf32float, 50)   # read only 50 elements\n```\n\n#### Advanced usage\n\nWhile the above basic usage pattern would be probably sufficient for most use\ncases, you can do the reading fully manually by calling the low-level read\nmethods. \n\nThe below code is an example for that; it approximates what `parseWaveFile()`\nis doing, minus the error checking. Consult the API docs for the list of\navailable functions.\n\n```nimrod\nvar wr = openWaveFile(\"example.wav\")\n\nvar cueChunk, listChunk, dataChunk: ChunkInfo\n\n# Iterate through all chunks\nwhile wr.hasNextChunk():\n  var ci = wr.nextChunk()\n  case ci.id\n  of FOURCC_FORMAT: wr.readFormatChunk(ci)\n  of FOURCC_CUE:    cueChunk = ci\n  of FOURCC_LIST:   listChunk = ci\n  of FOURCC_DATA:   dataChunk = ci\n  else: discard\n\nww.readRegions(cueChunk, listChunk)\n\n# Seek to the start of the sample data\nsetFilePos(wr.file, dataChunk.filePos + CHUNK_HEADER_SIZE)\n```\n\n### Writing WAVE files\n\nSimilarly to reading, writing WAVE files is accomplished through `WaveWriter`\nobjects.  A `WaveWriterError` will be raised if an I/O error was encountered\nor if you tried to perform an invalid operation (e.g. writing to a closed\nfile, attempting to write data between chunks etc.)\n\n#### Creating a WAVE file\n\nTo create a new WAVE file, a `WaveWriter` object needs to be instantiated\nfirst:\n\n```nimrod\nimport easywave\n\nvar ww = writeWaveFile(\n  filename = \"example.wav\",\n  format = wf16BitInteger,\n  sampleRate = 44100,\n  numChannels = 2\n)\n```\n\nNote that this will only create the file and write the master RIFF chunk\n(`\"RIFF\"`) header.\n\n#### Writing the format chunk\n\nYou'll need to explicitly call `writeFormatChunk()` to write the actual format\ninformation to the file in the form of a **format chunk** (`\"fmt \"`). This\ngives you the flexibility to optionally insert some other chunks before the\nformat chunk.\n\n#### Writing markers and regions\n\nTo write markers and regions to the file, you'll need to descibe them as a\ntable of values where the keys are the marker/region IDs (32-bit unsigned\nintegers unique per marker/region) and the values `Region` objects.\nMarkers are defined simply as regions with a length of zero.\n\n```nimrod\nww.regions = {\n  1'u32: Region(startFrame:     0, length:     0, label: \"marker1\"),\n  2'u32: Region(startFrame:  1000, length:     0, label: \"marker2\"),\n  3'u32: Region(startFrame: 30000, length: 10000, label: \"region2\")\n}.toOrderedTable\n\nww.writeRegions()\n```\n\nNote that the start positions and lengths of the markers/regions need to be\nspecified in sample frames—these are *not* byte offsets! (1 sample frame = *N*\nnumber of samples, where *N* is the number of channels)\n\n`writeRegions()` will technically create two new chunks right next to each\nother:\n\n* A **cue chunk** (`\"cue \"`) containing the IDs and the\n  start offsets of the cue points (markers)\n* A **list chunk** (`\"LIST\"`) containing label (`\"labl\"`) and labeled text\n  (`\"ltxt\"`) sub-chunks to store the labels and region lengths of the\n  markers/regions, respectively\n\nThe list chunks allows lots of other types of information to be stored in its\nvarious sub-chunks. If you need to store such extra data, you cannot use\n`writeRegions()`; you'll need to implement your own list chunk writing logic.\n\n\n#### Writing the data chunk and other chunks\n\nTo write any other other chunks types, you'll need to do the following:\n\n1. Call `startChunk(\"ABCD\")`, where `\"ABCD\"` is the 4-char chunk ID\n   ([FourCC](https://en.wikipedia.org/wiki/FourCC)).\n   `startDataChunk()` is a shortcut for creating the data chunk (`\"data\"`).\n\n2. Use the various `write*` methods to write the data (see the API docs for\n   the full list). Byte-order swapping will be handled automatically depending\n   on the CPU architecture and the endianness of the file. You need to ensure\n   that you use the correct write method variant for the particular sample\n   format you're using.\n\n3. When you're done, call `endChunk()` to close the chunk. This will pad the\n   data automatically with an extra byte at the end if an odd number of bytes\n   have been written so far, and it will update the chunk size field in the chunk\n   header.\n\n```nimrod\nww.startChunk(\"LIST\")\n\n# Write single values\nww.writeInt16(-442)\nww.writeUInt32(3)\nww.writeFloat64(1.12300934234)\n\n# Write buffered data\nvar buf16 = array[4096, int16]\nww.writeData(buf16)           # writeData methods take an openArray argument\n\nvar buf64float: seq[float64]\nww.writeData(buf64float, 50)  # write the first 50 elements only\n\nww.endChunk()\n```\n\nChunks can be nested; the library will make sure to calculate the correct\nchunk sizes for all parent chunks.\n\nBear in my mind that it is invalid to write data \"between chunks\"—an error\nwill be raised if you tried to write some data after ending a chunk but before\nstarting a new one.\n\n#### Closing the file\n\nFinally, the `endFile()` method must be called to update the master\nRIFF chunk with the correct master chunk size. This will also close the file.\n\n## Handling 24-bit data\n\nThe library provides two ways to deal with 24-bit data:\n\n* **As packed data:** `readData24Packed()` and `writeData24Packed()` treat\n    24-bit data as a continuous stream of bytes (as they actually appear in\n    the file). The first sample is bytes 1, 2 and 3, the second sample bytes\n    4, 5 and 6, and so on. Because of this, the size of the buffer used with\n    these two methods must be divisable by three, otherwise an assertion error\n    will be raised at runtime. The read and write methods only perform\n    byte-order swapping, if necessary.\n\n* **As unpacked data:** `readData24Unpacked()` and `writeData24Unpacked()`\n    treat 24-bit data as a stream of 32-bit integers. The read method unpacks\n    the packed data from the file into a stream of 32-bit integers (with the\n    most significant byte set to zero), while the write does the opposite.\n\nIt is important to stress out that the data will be always written to the WAVE\nfile in packed form—it's just sometimes more convenient to deal with 32-bit\nintegers than with packed data, hence the two different methods.\n\n\n## Some general notes about WAVE files\n\n* Little-endian WAVE files start with the `\"RIFF\"` master chunk ID, big-endian\n  files start with `\"RIFX\"`. Apart from the byte-ordering, there are no\n  differences between the two formats. The big-endian option is not really\n  meant to be used when creating new WAVE files; I just included it because\n  it made the testing of the byte-swapping code paths much easier on Intel\n  hardware.  Virtually nothing can read RIFX files nowadays, it's kind of a\n  dead format.\n\n* The only restriction on the order of chunks is that the format chunk *must*\n  appear before the data chunk (but not necessarily *immediately* before it).\n  Apart from this restriction, other chunks can appear in *any* order. For\n  example, there is no guarantee that the format chunk is always the first\n  chunk (some old software mistakenly assumes this).\n\n* All chunks must start at even offsets. If a chunk contains an odd number of\n  bytes, it must be padded with an extra byte at the end. However, the chunk\n  header must contain the *original unpadded chunk length* in its size field\n  (the writer takes care of this, but this might surprise some people when\n  reading files).\n\n## License\n\nCopyright © 2018-2014 John Novak \u003c\u003cjohn@johnnovak.net\u003e\u003e\n\nThis work is free. You can redistribute it and/or modify it under the terms of\nthe **Do What The Fuck You Want To Public License, Version 2**, as published\nby Sam Hocevar. See the `COPYING` file for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnnovak%2Feasywave","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnnovak%2Feasywave","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnnovak%2Feasywave/lists"}