Tuesday, May 31, 2011

Metronome for Linux in Python

I changed my metronome.c in python to work with Windows. ( Initially, I made a mistake which errored in Windows.) I didn't distinguish binary/text mode in Windows, which doesn't matter in Linux. After adding sys.platform condition, it works, but looks a little clumsy.

To use this,
$ python metronome.py [sample_file] [bpm] [duration in sec]


For example, this command will generate "a.wav" which has 120 BPM beat for 15 sec. s3.wav is a simple beep wav in 44100 wav encode.
$ python metronome.py s3.wav 120 15


Here is the code:
#!/usr/bin/env python

import os
import sys
import struct

empty_sound = struct.pack('bbbbbbbb', *( 4,0,0,0,6,0,6,0 ))

class WavHeader(object):
def __init__(self, rawdata):
self.tup = struct.unpack("iiiiihhiihhii", rawdata)
self.chunk_id = self.tup[0]
self.chunk_sz = self.tup[1]
self.format = self.tup[2]
self.sub_chunk1_id = self.tup[3]
self.sub_chunk1_sz = self.tup[4]
self.audio_format = self.tup[5]
self.num_channel = self.tup[6]
self.sample_rate = self.tup[7]
self.byte_rate = self.tup[8]
self.block_align = self.tup[9]
self.bits_per_sample = self.tup[10]
self.sub_chunk2_id = self.tup[11]
self.sub_chunk2_sz = self.tup[12]

def pack(self):
return struct.pack("iiiiihhiihhii", self.chunk_id, self.chunk_sz,
self.format , self.sub_chunk1_id,
self.sub_chunk1_sz , self.audio_format,
self.num_channel , self.sample_rate ,
self.byte_rate , self.block_align ,
self.bits_per_sample, self.sub_chunk2_id,
self.sub_chunk2_sz )

ONE_SEC = 88200
def bytes_for_beat(bpm):
ratio = bpm/60.0
bytes_per_beep = ONE_SEC / ratio
return int(bytes_per_beep)


def main():
sample_fname = sys.argv[1]
tempo = int(sys.argv[2])
if tempo < 40:
print >> sys.stderr, "Invalid tempo: Make it between 40 - MAX"
print >> sys.stderr, " * longer the sample length, smaller the MAX"
sys.exit(1)
dura = int(sys.argv[3])
if dura <= 0:
print >> sys.stderr, "Invalid duration: Make it greater than 0"
sys.exit(2)

# Reading sample header
if sys.platform == 'win32':
rd = open(sample_fname, "rb")
else:
rd = open(sample_fname, "r")
sample_hdr = WavHeader(rd.read(44))
sample_data = rd.read()
rd.close()

# Generating Beat data as WAV, storing temp file 't.wav'
if sys.platform == 'win32':
bdata = open("t.wav", "wb")
else:
bdata = open("t.wav", "w")
tot_beats = dura * (tempo/60.0);
bps = bytes_for_beat(tempo);
tot_bytes = 0;
for i in range( int(tot_beats)):
bdata.write( sample_data )
tot_bytes += sample_hdr.sub_chunk2_sz;
for j in range( (bps - sample_hdr.sub_chunk2_sz)/8):
bdata.write( empty_sound )
tot_bytes += len(empty_sound)
bdata.close()

# Overwrite new size, and Generate output wav file 'a.wav'
sample_hdr.sub_chunk2_sz = tot_bytes
if sys.platform == 'win32':
outf = open("a.wav", 'wb')
else:
outf = open("a.wav", 'w')
outf.write( sample_hdr.pack() )
if sys.platform == 'win32':
outf.write( open('t.wav', 'rb').read() )
else:
outf.write( open('t.wav').read() )
outf.close()


if __name__ == '__main__':
main()

No comments: