Log In  


@zep I was recently trying to make a dynamic soundtrack with Picotron but found it to be likely impossible. Ideally I'd like to switch between two tracks and have them sync up (if you're on beat 3 of measure 2 of track A, it should switch to beat 3 of measure 2 of track B). However, music() has no parameter to specify where to start a track. Since sfx() has an optional offset parameter, I can't imagine it would be hard to add, and it would help a lot with creating dynamic soundtracks or other things like pausing music and resuming at a specific point.

Edit: Alternatively, a good approach for creating dynamic soundtracks would be temporarily muting channels. Track A could be made on the first four channels, and Track B on the last four. Then, by changing which channels are muted, you could effectively switch between tracks. I don't know how this could be implemented, though.

3


Hi, +1 from me, because today I failed to port my dynamic music from PICO-8 to Picotron :)

In PICO-8 I was doing some "hacking" by covering the "off" music channel with a short looped empty SFX, then turning it "on" with stopping that SFX. In Picotron stopping the SFX with sfx(-1, …) means stopping the SFX of the music pattern as well, therefore I can no longer achieve that.

Ideally, I would have a way (at least some memory address to poke) to turn specific channels on and off.

And yes, while looking for a solution, I also realized the offset would be a good workaround in my case (and maybe a desired solution in some other case) :)


@beetrootpaul haha, I tried the exact same thing in picotron by muting a channel of music at a time. I'd done the same thing in Pico-8 and was annoyed to find it didn't work on Picotron too.


Some other approach I tried today, without success:

pokeing the music data:

enable_music_layers = function (layers)
    local channel_mask = 1
    if layers[1] then
        channel_mask = channel_mask + 2
    end
    if layers[2] then
        channel_mask = channel_mask + 4
    end
    if layers[3] then
        channel_mask = channel_mask + 8
    end

    -- Toggle channels of all 4 patterns of the music.
    poke(0x030100 + 9, channel_mask)
    poke(0x030100 + 29, channel_mask)
    poke(0x030100 + 49, channel_mask)
    poke(0x030100 + 69, channel_mask)
end

The adjusted channels apply only from the next started pattern, sadly. I assume Picotron reads an entire pattern from there, so any changes during its playback are not caught.

Then, I tried with note API, as it's said to be a low level control and it accepts 0xff as "do not change this param of a current note", so I can change just volume to 0. Attached to game's update loop, with mute_channel_<n> set in proper gameplay triggers separately:

update = function ()
    if mute_channel_1 then
        note(0xff, 0xff, 0, 0xff, 0xff, 1)
    end
    if mute_channel_2 then
        note(0xff, 0xff, 0, 0xff, 0xff, 2)
    end
    if mute_channel_3 then
        note(0xff, 0xff, 0, 0xff, 0xff, 3)
    end
end,

With this case the notes were still playing their tiny start portions. So, I guess, it ended up as a race between notes being played and update spamming them with "set the volume to 0 for the current note, plase"


1

This isn't tick-precise, but it can start from a specific row.

local floor = math.floor
local round = function(x) return floor(x+0.5) end

--- Starts playing music from a specific row of a pattern.
--- @param pattern integer The pattern to start playing from.
--- @param row integer? The row to start playing from. Defaults to 0.
--- @param fade_length integer? How many milliseconds the fading effect lasts. Defaults to 0.
--- @param channel_mask integer? An 8-bit mask of which channels will be enabled during playback. Defaults to 0xFF.
--- @param row_channel integer? Which channel the row refers to. Defaults to the lowest enabled.
local function play_from(pattern, row, fade_length, channel_mask, row_channel)
    row = row or 0
    channel_mask = channel_mask or 255

    if channel_mask & 255 == 0 or row == 0 then
        music(pattern, fade_length, channel_mask)
        return
    end

    local pattern_addr = 0x30100 + pattern * 20
    local tracks = {}
    local speeds = {}
    local row_offsets = {}
    local enabled = peek(0x30109 + pattern * 20) & channel_mask
    local lowest_enabled
    local tick_offset
    for i = 0, 7 do
        tracks[i] = peek(pattern_addr + i)
        speeds[i] = peek(0x050002 + 328 * tracks[i])
        if not lowest_enabled and enabled & 1 << i != 0 then 
            lowest_enabled = i
        end
    end

    row_channel = row_channel or lowest_enabled
    tick_offset = row*speeds[row_channel]

    for i = 0, 7 do
        row_offsets[i] = round(tick_offset / speeds[i])
    end

    local track_length_addr = 0x050003 + 328 * tracks[lowest_enabled]
    local pattern_length = peek2(track_length_addr)
    if pattern_length <= 0 then pattern_length = peek2(0x30022) end

    if pattern_length >= 0 then
        poke2(track_length_addr, max(pattern_length - row_offsets[lowest_enabled], 0))
        music(pattern, fade_length, channel_mask)
        poke2(track_length_addr, pattern_length)
    end

    for i = 0, 7 do
        if enabled & 1 << i != 0 then
            sfx(tracks[i], i, row_offsets[i])
        end
    end
end


[Please log in to post a comment]