Flarf in over his head making an entire frickin Dawtracker
BotB Academy Bulletins
 
 
209067
Level 10 Chipist
Da Flarf
 
 
post #209067 :: 2025.01.19 12:54pm :: edit 2025.01.19 1:00pm
  
  The Diad, nitrofurano and Luigi64 liēkd this
  
  ReiXbits hæitd this
So, recently a forum on here got me thinking about trackers.

https://battleofthebits.com/academy/GroupThread/39219/what%27s+a+really+specific+tracker+feature+that+you+wish+more+%28or+all%29+trackers+had/

What I'd like to see, but never have witnessed made properly is a .it tracker that uses DAW style notation to place notes. So I guess that means I gotta do it myself. I'm going to make it in godot engine because not having big libraries makes me hyperventilate.

I was looking at the source code for the impulse tracker format, but I saw that's written in assembly (assembly makes me puke). So, all that to say, is there source code for reading and writing impulse tracker files in more modern languages? (c++, java, etc.)

I don't need code for vst support. I just want to make a tracker that uses samples as instruments (if you can't tell I'm biased in favor of SPC)

If I can find it in a modern programming language I'll be able to convert it to godot engine's GDscript easily. Then the hard part will be making GUI responsible for writing the .it, which means a couple months from now you'll get another forum post :D
 
 
209068
Level 23 Chipist
syntheticgoddess
 
 
 
post #209068 :: 2025.01.19 12:55pm
what do you mean by "DAW-style notation"?
 
 
209070
Level 10 Chipist
Da Flarf
 
 
post #209070 :: 2025.01.19 12:59pm :: edit 2025.01.19 12:59pm
essentially, you open a pattern (capped at song pattern length) and you would select your channel then enter your notes in that pattern the same way you would in a daw (but with 1 polyphony max per channel) with note colors mapped to instruments. In a pop up window (like the velocity editor in FL studio) you could enter your note commands in .it format fashion
 
 
209071
Level 23 Chipist
syntheticgoddess
 
 
 
post #209071 :: 2025.01.19 1:01pm
not quite the same thing but that reminds me of http://users.notam02.no/~kjetism/radium/index.php
 
 
209072
Level 10 Chipist
Da Flarf
 
 
post #209072 :: 2025.01.19 1:07pm :: edit 2025.01.19 1:10pm
*sigh* yet another almost useful tool for my purposes
 
 
209073
Level 21 Grafxicist
Ahornberg
 
 
 
 
post #209073 :: 2025.01.19 1:13pm :: edit 2025.01.19 1:14pm
  
  dobra liēkd this
sounds like FamiStudio
and this is open source
 
 
209074
Level 10 Chipist
Da Flarf
 
 
post #209074 :: 2025.01.19 1:16pm :: edit 2025.01.19 1:19pm
Precisely! My intent is to make a DAW that exports .it for use in SNESmod

GAWDARNIT parts of Famistudio's written in assembly! And it doesn't use tracker formats!!!!! AAAAUUUUGHHHHH
 
 
209078
Level 28 Chipist
Jangler
 
 
 
post #209078 :: 2025.01.19 1:36pm
i think the assembly parts are just NES assembly for the sound engine
 
 
209080
Level 25 Grafxicist
Webriprob
 
 
 
post #209080 :: 2025.01.19 1:38pm :: edit 2025.01.19 1:40pm
You could try ptcollab or Pxtone. The ptcop file is missing some of the features of .it but for the most part I’d say it’s the best piano roll alternative to module files
(edit nvm it doesn’t work with snesmod)
 
 
209086
Level 10 Chipist
Da Flarf
 
 
post #209086 :: 2025.01.19 2:25pm
I'm willing to settle for using the assembly implementation of the impulse tracker format as long as there's an implementation to both read and write .it written in a more modern language
 
 
209090
Level 20 Mixist
Luigi64
 
 
 
post #209090 :: 2025.01.19 2:37pm
here is some old code I wrote to just import them on a real basic level, but you're gonna need more than that for a real tracker. you might wanna take a look at https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_it.cpp


@tool
extends EditorImportPlugin

func _get_importer_name():
return "it_import"

func _get_visible_name():
return "ITModule"

func _get_recognized_extensions():
return ["it", "mptm"]

func _get_save_extension():
return "res"

func _get_resource_type():
return "Resource"

func _get_preset_count():
return 0

func _get_import_options(_i, _j):
return []

func _get_import_order():
return 0

func _get_priority():
return 0


func _import(source_file, save_path, options, platform_variants, gen_files):
var module = preload("resource.gd").new()

var file = FileAccess.open(source_file, FileAccess.READ)

file.seek(4)

module.name = file.get_buffer(26).get_string_from_ascii()
module.highlight_a = file.get_8()
module.highlight_b = file.get_8()

var order_num = file.get_16()
var instrument_num = file.get_16()
var sample_num = file.get_16()
var pattern_num = file.get_16()

file.seek(0x32)
module.ticks = file.get_8()
module.tempo = file.get_8()
file.seek(0x36)
var msg_length = file.get_16()
file.seek(file.get_32()) # msgoffset
module.message = file.get_buffer(msg_length).get_string_from_ascii()

file.seek(0xC0)
module.orders = file.get_buffer(order_num)

file.seek(0xC0 + order_num)

var instrument_offsets = []
for i in instrument_num:
instrument_offsets.append(file.get_32())

var sample_offsets = []
for i in sample_num:
sample_offsets.append(file.get_32())

var pattern_offsets = []
for i in pattern_num:
pattern_offsets.append(file.get_32())

for i in instrument_offsets:
var instrument = {}

file.seek(i + 4)

instrument.file_name = file.get_buffer(12).get_string_from_ascii()
file.seek(i + 17)
instrument.new_note_action = file.get_8()
instrument.duplicate_check_type = file.get_8()
instrument.duplicate_check_action = file.get_8()

instrument.fade_out = file.get_16()

instrument.pitch_pan_separation = file.get_8()
instrument.pitch_pan_center = file.get_8()
instrument.global_volume = file.get_8()
instrument.default_pan = file.get_8()
instrument.random_volume_variation = file.get_8()
instrument.random_panning_variation = file.get_8()
var tracker_version = file.get_16()
var sample_count = file.get_8()
file.seek(i + 32)
instrument.name = file.get_buffer(26).get_string_from_ascii()

instrument.initial_filter_cutoff = file.get_8()
instrument.initial_filter_resonance = file.get_8()

instrument.midi_channel = file.get_8()
instrument.midi_program = file.get_8()
instrument.midi_bank = file.get_8()

instrument.table = file.get_buffer(240)
instrument.volume_envelope = {}
instrument.panning_envelope = {}
instrument.pitch_envelope = {}


for envelope in [
instrument.volume_envelope,
instrument.panning_envelope,
instrument.pitch_envelope
]:
var flags = file.get_8()
envelope.enabled = (flags & 1) != 0
envelope.loop = (flags & 2) != 0
envelope.sustain_loop = (flags & 4) != 0
if envelope == instrument.pitch_envelope:
envelope.sustain_loop = (flags & 128) != 0

var point_num = file.get_8()
envelope.loop_begin = file.get_8()
envelope.loop_end = file.get_8()
envelope.sustain_loop_begin = file.get_8()
envelope.sustain_loop_end = file.get_8()
envelope.points = []
for j in 25:
var point = {}
var y = file.get_8()
var x = file.get_16()
point.position = Vector2i(x, y)
envelope.points.append(point)

module.instruments.append(instrument)

for i in sample_offsets:
var sample = ITSample.new()

file.seek(i + 4)

sample.file_name = file.get_buffer(12).get_string_from_ascii()
file.get_buffer(12)
file.seek(i + 17)
sample.global_volume = file.get_8()

var flags = file.get_8()
sample.has_sample = (flags & 1) != 0
sample.use_16_bit = (flags & 2) != 0
sample.stereo = (flags & 4) != 0
sample.compressed = (flags & 8) != 0
sample.loop = (flags & 16) != 0
sample.sustain_loop = (flags & 32) != 0
sample.ping_pong_loop = (flags & 64) != 0
sample.ping_pong_sustain_loop = (flags & 128) != 0

sample.volume = file.get_8()

sample.name = file.get_buffer(26).get_string_from_ascii()
sample.cvt = file.get_8()
sample.default_pan = file.get_8()
var sample_length = file.get_32()
sample.loop_begin = file.get_32()
sample.loop_end = file.get_32()
sample.c5_speed = file.get_32()
sample.sustain_loop_begin = file.get_32()
sample.sustain_loop_end = file.get_32()

var sample_pointer = file.get_32()

sample.vibrato_speed = file.get_8()
sample.vibrato_depth = file.get_8()
sample.vibrato_rate = file.get_8()
sample.vibrato_type = file.get_8()

file.seek(sample_pointer)

# does not yet decompress samples and so may go out of bounds
var nice = []
if sample.use_16_bit:
for j in sample_length:
nice.append(file.get_16())
else:
nice = file.get_buffer(sample_length) as Array

sample.samples = PackedInt32Array(nice)
module.samples.append(sample)


for i in pattern_offsets:
file.seek(i)
file.get_16() # patlength
var row_num = file.get_16()
file.get_32()

var pattern = []
pattern.resize(row_num)
module.patterns.append(pattern)

var mask = {}
var note = {}
var instrument = {}
var volume = {}
var command = {}
var command_value = {}

var row = 0

while row < row_num:
var channel_variable = file.get_8()
if channel_variable == 0:
row += 1
else:
var channel = channel_variable - 1 & 63

if channel_variable & 128:
mask[channel] = file.get_8()

if pattern[row] == null:
pattern[row] = {}

pattern[row][channel] = {}

if mask[channel] & 1:
note[channel] = file.get_8()
if mask[channel] & 2:
instrument[channel] = file.get_8()
if mask[channel] & 4:
volume[channel] = file.get_8()
if mask[channel] & 8:
command[channel] = file.get_8()
command_value[channel] = file.get_8()

if mask[channel] & 1 + 16:
pattern[row][channel].note = note[channel]
if mask[channel] & 2 + 32:
pattern[row][channel].instrument = instrument[channel]
if mask[channel] & 4 + 64:
pattern[row][channel].volume = volume[channel]
if mask[channel] & 8 + 128:
pattern[row][channel].command = command[channel]
pattern[row][channel].command_value = command_value[channel]

return ResourceSaver.save(module, save_path + "." + _get_save_extension(), ResourceSaver.FLAG_COMPRESS)





resource.gd
@tool
extends Resource


@export var name:String
@export var highlight_a:int
@export var highlight_b:int
@export var instruments:Array[Dictionary]
@export var samples:Array[ITSample]
@export var patterns:Array[Array]
@export var orders:PackedByteArray
@export var ticks:int
@export var tempo:int
@export var message:String

sample.gd
@tool
class_name ITSample
extends Resource
@export var file_name:String
@export var global_volume:int
@export var has_sample:bool
@export var use_16_bit:bool
@export var stereo:bool
@export var compressed:bool
@export var loop:bool
@export var sustain_loop:bool
@export var ping_pong_loop:bool
@export var ping_pong_sustain_loop:bool
@export var volume:int
@export var name:String
@export var cvt:int
@export var default_pan:int
@export var loop_begin:int
@export var loop_end:int
@export var c5_speed:int
@export var sustain_loop_begin:int
@export var sustain_loop_end:int
@export var samples:PackedInt32Array
@export var vibrato_speed:int
@export var vibrato_depth:int
@export var vibrato_rate:int
@export var vibrato_type:int
 
 
209094
Level 16 Chipist
Stupe
 
 
post #209094 :: 2025.01.19 2:54pm
  
  Collidy, pedipanol, retrokid104, puke7, SRB2er, big lumby, Prestune, Jangler and blower5 liēkd this
Easier to write a new program than to learn to track modules???
 
 
209097
Level 23 Chipist
Opilion
 
 
 
post #209097 :: 2025.01.19 3:49pm
  
  Collidy liēkd this
I never tried it myself but if you are looking for a piano roll tool to make .it modules, maybe Sound Club
is for you!

It will actually output .s3m modules instead of .it but you can easily make the .s3m -> .it conversion by using OpenMPT (you won't need to use the tracker interface at all for this ;D )
 
 
209098
Level 26 Chipist
blower5
 
 
 
post #209098 :: 2025.01.19 3:58pm
if you like using a daw to make .spc so much isn't the C700 vst a much better solution than, basically, programming your own version of pxtone?
 
 
209100
Level 23 Chipist
Opilion
 
 
 
post #209100 :: 2025.01.19 4:29pm
I may be wrong but I'm afraid C700 isn't a viable option for .spc as it manage data less efficiently than programs like snesmod. There is a section about it in damifortune's guide
(at the end of the part called "C700 vs SNESMOD")
 
 
209104
Level 10 Chipist
Da Flarf
 
 
post #209104 :: 2025.01.19 6:33pm :: edit 2025.01.19 6:46pm
  
  Ahornberg liēkd this
@stupe

I know trackers. I know DAWS. I like both. I want to combine them both into something new.

@opillion

I know. That's part of the reason why I use SNESmod. I still use c700 to test how much space my samples will take tho

@blower 5
My c700 vst stopped exporting for some reason, so now I'm FORCED to use SNESmod


(edit) just had an idea. I saw that sound club is open source so I guess I'll just make a user friendly port of it with better GUI in godot engine and use OpenMPT to convert to .it

(edit #2) not open source :(
 
 
209106
Level 23 Chipist
syntheticgoddess
 
 
 
post #209106 :: 2025.01.19 6:48pm
  
  Collidy and Opilion liēkd this
@opilion - it is also noted in that document that the .smc export in C700 more than makes up for any inefficiencies
 
 
209107
Level 10 Chipist
Da Flarf
 
 
post #209107 :: 2025.01.19 6:51pm
@synthetic goddess

I'm aware of the .smc export but I want to export my music in a format a dev would have while the system was in its hayday. I'd also like to keep my piano roll in the process without any significant tradeoffs... thus my quest to make an entire frickin daw
 
 
209108
Level 23 Chipist
syntheticgoddess
 
 
 
post #209108 :: 2025.01.19 6:53pm
i see! godspeed
 
 
209109
Level 10 Chipist
Da Flarf
 
 
post #209109 :: 2025.01.19 7:07pm :: edit 2025.01.19 7:19pm
After running into a dead end (sound club is not open source and was developed by 5 random guys from Estonia), I need a method of attack here.

I downloaded soundclub and installed it. Now I just need to find some way to rip it. Before you start yapping at me about the legality of it all, I'm gonna need you to consider the legally questionable things us chipists do to make our trash music.

With that being said, I found a couple of files that visual studio 2022 can't open within the files I installed. Anyone got a clue on how to rip this stuff?


(edit) WTF ONE OF THE SOUNDCLUB DEVS WENT ON TO MAKE SKYPE
https://en.wikipedia.org/wiki/Jaan_Tallinn
http://www.bluemoon.ee/team/jaan/index.html
 
 
209112
Level 27 Mixist
doctorn0gloff
 
 
 
post #209112 :: 2025.01.19 8:45pm
  
  SRB2er liēkd this
i'm all for developing custom, opinionated tools for learning and cool musicking, but I think you're going down an unnecessarily difficult path...

here's the .it file specification copied straight from the author
https://github.com/schismtracker/schismtracker/wiki/ITTECH.TXT

here are OpenMPT's notes on the format
https://wiki.openmpt.org/Development:_Formats/IT

finally here is a python library that can read IT modules. There's no write functionality yet, but I think it seems clear enough (plus the help of ITTECH.TXT) for you to write a writer based on this.
https://github.com/ramen/pytrax/blob/master/pytrax/impulsetracker.py

good luck :]
 
 
209135
Level 23 Chipist
Opilion
 
 
 
post #209135 :: 2025.01.20 4:16am
I just thought about this: since it seems what you are really aiming for is making .spc music, wouldn't it be easier to write a program that returns MML code instead of a .it module?

You could then use a MML compiler like Terrific Audio Driver
or SuperC
to convert it to .spc.

@syntheticgoddess: good reminder! I didn't want to imply C700 is a bad tool in general
 
 
209147
Level 10 Chipist
Da Flarf
 
 
post #209147 :: 2025.01.20 8:01am :: edit 2025.01.20 10:39am
  
  Opilion liēkd this
@opillion
MML code has always confused me. I've never used it to compose a SNES song. I've pretty much just used C700 and SNESmod. From what you've said, apparently it's easier to write a program for it instead of .it?

I don't have an inkling of an idea for how to make a program that returns MML. Anybody have a good starting point?

(edit) I looked at the documentation to superC. I think I can see where opillion is coming from. I could have a godot script automatically update the "#tone" section of an MML txt whenever the user imports a .brr sample. The next step would be converting midi notes in the piano roll to note instructions within the MML. After that, I'd need to implement effects and separate scripts to convert that into effect commands within the MML. Finally, once I got that done, I'd have to somehow implement preview playback from within the editor, without having godot create an .spc file (which I would have to write code to play back anyway)

(edit #2) WTF superC doesn't have vibrato effect? I might use terrific audio driver instead... if only I can figure out how to build it

(edit #3) wait a minute I found that superC has modulation. That's basically vibrato. It's a lot more documented too so I'm just gonna use that
 
 
209152
Level 23 Chipist
Opilion
 
 
 
post #209152 :: 2025.01.20 10:00am
Yes that's what I meant! A MML file is just like a text file (but with a .mml extension instead of .txt) so you can open your file "song.mml" from your program and write the MML instructions using character strings (you should be able to do it in most programming languages).

That being said, it seems indeed that the biggest difficulty will be to transcribe each feature of your music program into MML code :o

I tried to install Terrific Audio Driver once (on Linux) but I couldn't compile a dependency called wiz, there should be a way to do it though, I might try again another time.
 
 
209155
Level 10 Chipist
Da Flarf
 
 
post #209155 :: 2025.01.20 11:53am
  
  Opilion liēkd this
@opillion
Thanks for the inspiration so far!

Update:

I've been researching the ins and outs of how to read and write to files in godot for about an hour now, so I think to get started I'll make my program generate a .txt file and put the responsibility of changing the file type to .mml on the user (all they have to do is a simple file rename)

However, now I have a new obstacle. Godot only supports mp3, wav, and ogg for audio, so I somehow have to get the program to import user imported BRR files at runtime and save it in the same folder as the project.
 
 
209157
Level 23 Chipist
Opilion
 
 
 
post #209157 :: 2025.01.20 12:04pm
If it's possible to run external commands (I mean shell commands for instance if you're on Linux or macOS) from Godot, maybe you can use snesbrr
.

Another option would be to ask the user to convert their samples to .wav beforehand, using a program like snesbrr or C700 (the later doesn't require to use the command line!). I believe this is what you have to do when you want to use snesmod for instance (as you can't load BRR samples in .it modules).
 
 
209159
Level 10 Chipist
Da Flarf
 
 
post #209159 :: 2025.01.20 12:21pm :: edit 2025.01.20 2:12pm
  
  Opilion liēkd this
That's a reasonable solution for now. I could have the user convert all their samples to wav and throw them into the project for being able to preview their song.

However, in the future, I will need to support a brr import option because:
1. the goal of my project is to remove the necessity to script in mml from the user
2. brr samples are required to be in a folder labeled samples in the same folder as the mml file https://github.com/Zexxerd/SuperC-SPCdrv-English-Translated-Wiki/wiki/How-to-create-SPC
3. I don't want inexperienced chiptuners complaining about why their non 16 bit sample sounds like garbage in the output spc

One solution I could do (that would require me forking snesbrr) is to to turn snesbrr into a godot plugin and run a script that'll call the extension to convert imported wavs to brr and save them in the right folder. That could cause an issue where the wrong instrument name is saved to the mml breaks or to where instruments get overwritten, but that's a problem for later

How do I build snesbrr? It's been forever since I've used c++ and I can't find a .sln file

(edit) nvm I found the windows release
 
 

LOGIN or REGISTER to add your own comments!